How to point a triangle at the tip of a line [R] - r

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

Related

How do I make an xspline curve symmetric in R?

I'm trying to draw a shape using the xspline function in R.
Using a set of control points, I can get the shape but it is asymmetric even though the points and shape values are all symmetric.
How do I draw this shape symmetrically?
This draws the approximate shape but the lines show how it is asymmetric.
curve <- data.frame(x=c(-0.1,-0.1,-0.1,-0.1,0.1,0.1,0.1,0.1,0.1,0.1,-0.1,-0.1,-0.1),y=c(0.1,0.1,0.1,0.3,0.3,0.1,0.1,-0.1,-0.1,-0.3,-0.3,-0.1,-0.1))
plot(curve)
xspline(curve,shape=1,open=F)
lines(x=c(-0.15,0.15),y=c(0.15,0.15),col="red")
lines(x=c(-0.15,0.15),y=c(-0.15,-0.15),col="red")
I have tried changing the shape values for each node but with no success.
Your question are actually two questions in one:
Is the curve (as a mathematical object) symmetric with respect to the x-axis?
Does it seem so in the picture?
Answer 2
Even if Answer 1 were "Yes" (which I doubt, see below), I think the answer is "No." Judging from the documentation, what xspline does is that it evaluates the curve at many points and then plots a polyline connecting these. You can persuade yourself: setting draw to F, the following should give you two arrays, one of x- and one of y-values.
curve <- data.frame(x=c(-0.1,-0.1,-0.1,-0.1,0.1,0.1,0.1,0.1,0.1,0.1,-0.1,-0.1,-0.1),y=c(0.1,0.1,0.1,0.3,0.3,0.1,0.1,-0.1,-0.1,-0.3,-0.3,-0.1,-0.1))
plot(curve)
pts=xspline(curve,shape=1,open=F,draw=F)
pts
I don't think there is any way of controlling the number or density of the evaluation points. So even if your curve (as a mathematical object, blue) is symmetric, its polyline rendering (black) is not necessarily:
This alone might explain the small differences from #Mike's comment.
Answer 1
We don't know exactly how R enforces the curve being closed. Based on the documentation,
For open X-splines, the start and end control points must have a shape of 0 (and non-zero values are silently converted to zero).
I suppose that it adds another control point at the very end, makes it equal to the first one and sets the shape of both of them equal to zero. But this is different from what your control points on the right hand-side of the picture look like! Your control point (0.1, 0.1) is repeated twice (not three times as (-0.1, 0.1) is) and its shape is 1, not 0 (caveat: the control point being repeated three times, maybe this does not have any influence; we would have to check the paper linked from the documentation).
I have adapted this and plotted the curve and its mirrored version so that we see the difference.
curve <- data.frame(
x=c(-0.1,-0.1,-0.1,-0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,-0.1,-0.1,-0.1),
y=c( 0.1, 0.1, 0.1, 0.3, 0.3, 0.1, 0.1, 0.1,-0.1,-0.1,-0.3,-0.3,-0.1,-0.1))
xswap <- data.frame(
x=c( 0.1, 0.1, 0.1, 0.1,-0.1,-0.1,-0.1,-0.1,-0.1,-0.1,-0.1, 0.1, 0.1, 0.1),
y=c( 0.1, 0.1, 0.1, 0.3, 0.3, 0.1, 0.1, 0.1,-0.1,-0.1,-0.3,-0.3,-0.1,-0.1))
plot(curve)
xspline(curve,shape=c(0,1,1,1,1,1,1,0,1,1,1,1,1,1),open=F)
xspline(xswap,shape=c(0,1,1,1,1,1,1,0,1,1,1,1,1,1),open=F)
lines(x=c(-0.15,0.15),y=c(0.15,0.15),col="red")
lines(x=c(-0.15,0.15),y=c(-0.15,-0.15),col="red")
To me they seem pretty much overlapping, especially taking the effects from Answer 2 into account.
General recommendations
Unless absolutely necessary, do not repeat control points. This tends to have funny effects on the underlying parametrization. More often than not (in my opinion), repeated control points come from people confusing control points with knots.
When you want symmetry with respect to the x-axis, put the first (and last) control point on it. Then you don't have to worry about finding the corresponding control point whose shape you have to set to 0.
For example,
curve <- data.frame(
x=c(0, 1, 1, 0, -1, -1),
y=c(1, 1, -1, -1, -1, 1))
curve_x_swapped <- data.frame(
x=c(0, -1, -1, 0, 1, 1),
y=c(1, 1, -1, -1, -1, 1))
plot(curve)
xspline(curve, shape=1,open=F)
xspline(curve_x_swapped,shape=1,open=F,border="red")

Is there an R function to draw a solid circle with a radius in user coordinates

Is it possible to draw real solid circle with a radius in "user" coordinates?
I tried the following:
Polygons:
I don't want to use them because I need real circles in the resulting svg.
Segments
segments(x, y, x, y, lwd=px, lend=0)
With segments there is the problem that I don't find a way to specify the segment in "user" coordinates.
The resulting graph is at the end exported to PDF.
Update
I draw a graph with a lot of elements and the elements has a distinct width. The width of the elements depends on the width at the x-axis. If I don't use user coordinates the result in the PDF is not correct in dependence to the x-axis.
A Polygon is an approximation to a circle and if I use them the result e.g. PDF is very large and the performance is not good and memory usage is very high. I draw 10,000 circles and more on one graph.
I use the following code with the described performance problems:
circle <- function(x, y, r, col) {
edgeCount <- 50
intervals <- (1:edgeCount) / edgeCount * 2 * pi
for(i in 1:length(x)) {
polygon(r[i]*sin(intervals) + x[i], r[i]*cos(intervals) + y[i], col=col[i],border=NA)
}
}
If you're comfortable with using a wrapper for sp's SpatialLine object you can try the oceanmap package which has a quite useful function called SpatialCircle(). It essentially builds a circle via seq() and adjusts it for your center point coordinates x and y, and for your radius r. It's still a set of line segments (so not one curved line), but quite simple to use.
Result:
Code:
Pretty straightforward:
# Load libraries.
library(oceanmap)
# Generate plot window and data.
set.seed(1702)
plot.new()
plot.window(xlim = c(0, 20), ylim = c(0, 10),
asp = 1, xaxs = "i", yaxs = "i")
axis(1)
axis(2)
box()
n <- 1000
x <- runif(n, 0, 20)
y <- runif(n, 0, 10)
for (i in 1:n) {
circle <- SpatialCircle(x = x[i], y = y[i], r = 0.1, n = 1000)
lines(circle)
}
This also works with ggplot2 with some data wrangling.
Addendum: Precision of SpatialCircles
If you want to check out what n (precision) in the SpatialCircle() function really means, try the following:
nrow(circle#lines[[1]]#Lines[[1]]#coords)
Result:
[1] 1000
This means that the object has 1,000 coordinate pairs (x and y) through which a line can be drawn. Furthermore, this line will have 999 distinct line segments, as the first and the last coordinate pairs are always identical. Proof:
all.equal(circle#lines[[1]]#Lines[[1]]#coords[1, ],
circle#lines[[1]]#Lines[[1]]#coords[1000, ])
Result:
[1] TRUE
If found a solution myself with the help of Gregor2 which did lead me to the library "grid".
library(grid)
#draw frame using normal plot
plot(0, 0, cex=0)
margins <- par("mar")
#1: bottom 2:left 3:top 4:right
mb <- unit(margins[1], "lines")
ml <- unit(margins[2], "lines")
mt <- unit(margins[3], "lines")
mr <- unit(margins[4], "lines")
#create viewport equivalent to margins in par
pushViewport(viewport(x = ml, y = mb, width = unit(1, "npc") - ml - mr, height = unit(1, "npc") - mb - mt, just=c("left", "bottom"), clip=TRUE))
#draw circle in npc units (easily convertable to user units using grconvertX)
grid.draw(circleGrob(x=0.5, y=0.5, r=0.5, default.units="npc", gp=gpar(col="blue", fill="blue")))
popViewport()

how to change the size, color of points in a scatter plot in R

You can find the example data in below
I want to color, recognise those points higher than 0 in another color and lower than 0 in another color. Is there any way to know which points are they ? I simply want to add a border higher and lower -1 and then say show those point higher than 1 in another color and print their name close to it while the same for lower than -1 but another color
This comment did not help since make read line randomly
x=(1:990)
cl = 1*(z>0) + 2*(z<=0)
cx = 1*(z>0) + 1.2*(z<=0)
plot(y~x, col=cl, cex=cx)
I don't want to generate red and black points around zero.
I want to detect those points higher and lower than 1 and -1 respectively.
I also want to plot them in different color and different size
Generate some data around 0:
d<-rnorm(1000,0,1)
To get the points higher than 0:
d[d>0]
To identify the index of points higher than 0:
which(d>0)
Plot points above 0 in green below 0 in red. Also, points above 0 will be a different size than points below 0:
s <- character(length(d))
s[d>0] <- "green"
s[d<0] <- "red"
# s[d > -0.5 & d < 0.5] <- "black" # to color points between 0.5 and -0.5 black
plot(d, col=s) # color effect only
sz <- numeric(length(d))
sz[d>0] <- 4 # I'm giving points greater than 0 a size of 4
sz[d<0] <- 1
plot(d, col=s, cex=sz) # size and color effect
Now, you also mention points above and below 1 and -1, respectively. You should be able to follow the code above to do what you want.
To add labels to points meeting a certain condition (e.g. greater than or less than 0.2 and -0.2, respectively), you can use the text function:
text(which(abs(d) > .2), d[abs(d) > .2], cex = 0.5, pos=3)
pos = 3 means to put the label above the point, and the cex argument to text is for adjusting the label size.
As the comments mentioned, there are many ways of doing this. Assuming that you are using the plot() function, here's a simple way of doing what you want. The key is to understand the arguments of plot(). Color of points is determined by col, size by cex, and so forth. These should all be vectors of the same size of y (else the recycling rule is used). See ?plot.
N = 999 # I don't care how many obs you have
y = rnorm(N)
# vector of colors (black for y>0, red for y<=0)
cl = 1*(y>0) + 1.2*(y<=0)
# vector of point sizes relative to default (1 for y>0, 1.2 y<=0)
cx = 1*(y>0) + 1.2*(y<=0)
plot(y, col=cl, cex=cx)
Edit:
I tried to give a general example (eg, coloring points by a third variable), but OP insists he had 2 variables. Well, just rename z by say x.
Edit:
# last edit I make
set.seed(1)
y = rnorm(N)
cl = rep(1, length(y))
cl[y > 0.5] = 2
cl[y < -0.5] = 3
plot(y, col=cl)
And here's what it gives:

Adding transparent circles of defined radius to existing plot in R

I have a data.frame with X and Y coordinate values. X axis is position information and Y axis is log ratio values. The points are colored based log ratio values(green > 0.25 , -0.25 < grey < 0.25, and red < -0.25). The orange dashed horizontal lines are log2 values of 0.58, 0, and -1.
A circular binary segmentation algorithm segments changes in log ratio, indicated by horizontal blue line. In the image attached one can see several segments, most if it close to log2 of 0. Close to the left end of the figure are small blue segment with log value close to 0.58, and a much smaller (almost invisible because of surrounding red points) blue segment at log value close to -1 (right edge of plot). I have x and y coordinates of these blue segments in another data.frame. I want to achieve the following
1) add circles bounding these blue segments above -0.70 < log2 > 0.50. This helps in identifying small segments that could be missed
2) Add transparent colors to these circles using alpha values so that the blue segment is seen
3) The size of the circle would be based on the width of these blue segments.
I am also open to other ideas of highligting these blue segments at -0.70 < log2 > 0.5. Maybe I should suppress plotting the points (green and red) where these blue segments are found. I am using R to make this plot. Appreciate the help.
This was the code used: There are two df objects. The df(X) contains Chr.no, Chr.Start, Chr.End and Log2. The df(Y) is similar, but different col.names such as loc. start, and loc. end. And instead of Log2, they have seg.mean values
for (i in 1:25) { # Plot each chromosome separately
plot(X[which(X$Chr.No ==i),"Chr.Start"], X[which(X$Chr.No ==i),"Log2"], ylim=c(-4.0,4.0), col=X[which(X$Chr.No ==i),"Color"], pch=16, cex=0.4, ylab="Log2", xlab="Genomic Position", main= paste("KCL:180522_SS", "chromosome", i, sep=" "))
abline(h=c(-1,0,log2(3/2)), lty=2, col="chocolate")
xleft = Y[which(Y$Chr.No ==i),"loc.start"] # Left limit of the blue horizontal line
xright = Y[which(Y$Chr.No ==i),"loc.end"] # Right limit of the blue horizontal line
ybottom= Y[which(Y$Chr.No ==i),"seg.mean"] - 0.010 # Adding thickness to the "seg.mean"
ytop = Y[which(Y$Chr.No ==i),"seg.mean"] + 0.010 # Adding thickness to the "seg.mean"
rect(xleft=xleft, ybottom=ybottom, xright=xright, ytop=ytop, col="blue", border="blue")
}
#Dwin Yes, "Color" is a vector of "lightgreen", "grey" and "red". These are the color information for the pch=16 in the plot(x,y). I do not want to modify the pch=16 points. The horizontal "blue" line segments are added by the 'rect', and they span many pch=16 points. As you can see there are many "blue" segments, some very small and some large in length that differ in their log2 values.This is what I want to bound with a filled transparent circle. Not all "blue" segments, but only the ones where the "blue" segment 0.25< log2 > 0.25. In this figure the smaller "blue" segments are close to the edges of the plot, and since they are difficult to spot, I want to highlight them with a filled circle around them. Please let me know if I am still not clear. Thanks
(Deleted incorrect method based on guess about the manner in which the blue points (which were really segments) were being constructed.)
Edit: With the new information I would suggest drawing ordinary "points", i.e, open circles at the x-vector formed by (xleft+xright)/2 and the y-vector using ytop (which should be the same as ybottom) each for the selected ytop values that meet your criteria. You would make a logical vectors to select each of these vectors. So:
selvec <- ytop < -0.70 | ytop > 0.5
points ( x= (xleft[selvec]+xright[selvec])/2, y= ytop[selvec], cex =1.5, col="blue")
You could also use transparency if you used the rgb() function to create a color with transparency:
points ( x= (xleft[selvec]+xright[selvec])/2, y= ytop[selvec], cex = 2, col=rgb(0, 0, 1, 0.3) )
.... should give you transparent circles if your output device supports it.

How to specify the margin of a plot in mm/cm using matplotlib?

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])

Resources