The picture below explains what I'd like to achieve in R. The blue line is added with a simple call to lines. This line shows some values of parameter over time. The line is thick (lwd=3) so it can be seen at a low zoom, and also so that the individual data points fit inside of it. This means that it overlaps with itself when there is chatter over a small x interval. Can the line be made to have a border to better resolve areas with overlap?
Draw a thicker line first and then draw thinner line on top of it
set.seed(42)
x = 1:10
y = sample(1:10)
plot(x, y, type = "l", lwd = 5, col = "red")
lines(x, y, lwd = 3, col = "green")
An alternative.
Under the assumption that seeing borders in order and on top of the "fill" color is important, then borrowing from d.b's excellent, fast, and very-simple answer that intersections give no indication of which direction came first (left of the 3-pack, below).
Altered data, to show overlaps:
set.seed(42)
x <- sample(10)
y <- sample(10)
One solution is to draw each segment individually, controlling colors each time.
segments2 <- function(x, y, lwd = c(2, 1), ..., border = NA, fill = NA) {
stopifnot(length(x) == length(y))
len <- length(x)
ign <- Map(function(x0, y0, x1 = x0, y1 = y0) {
if (!is.na(border)) lines(c(x0, x1), c(y0, y1), lwd = lwd[1], col = border, ...)
if (!is.na(fill)) lines(c(x0, x1), c(y0, y1), lwd = lwd[2], col = fill, ...)
}, x[-len], y[-len], x[-1], y[-1])
invisible()
}
This produces the middle plot below, but notice that each vertex has border-intrusion. A third option, much more complex, can mitigate that with a lot of trickery.
segments3 <- function(x, y, lwd = c(2, 1), ..., border = NA, fill = NA, lend = 0) {
stopifnot(length(x) == length(y))
len <- length(x)
dx <- x[-len] + diff(x) / 2
mx <- rbind(
c(NA, x[1], dx[1]),
cbind(dx[-(len-1)], x[-c(1,len)], dx[-c(1)]))
mx <- rbind(
mx[-(len-1),],
c(x[len], dx[len-1], NA),
mx[len-1,])
dy <- y[-len] + diff(y) / 2
my <- rbind(
c(NA, y[1], dy[1]),
cbind(dy[-(len-1)], y[-c(1,len)], dy[-c(1)]))
my <- rbind(
my[-(len-1),],
c(y[len], dy[len-1], NA),
my[len-1,])
for (rn in seq_len(nrow(mx))) {
lend0 <- if (rn %in% c(1L, len-1)) lend else 1
lines(mx[rn,], my[rn,], lwd = lwd[1], col = border, ..., lend = lend0)
lines(mx[rn,], my[rn,], lwd = lwd[2], col = fill, ..., lend = lend0)
}
}
It is likely possible to simplify this, but its performance is not heinous, and it does produce a slightly clearer plot.
par(mfrow=c(1, 3))
# simpler method
plot(x, y, type = "l", lwd = 5, col = "red", main = "Simpler")
lines(x, y, lwd = 3, col = "green")
# slower method, full-size
plot(x, y, type = "n", main = "Slower")
segments2(x, y, lwd = c(5, 3), border="red", fill="green")
# slowest method, fairly complex
plot(x, y, type = "n", main = "Slowest")
segments3(x, y, lwd = c(5, 3), border="red", fill="green")
Some notes:
lwd is a length-2 vector used to control the widths of the two lines; perhaps it would be more intuitive to have lwd be the main line and border.lwd be the border's thickness?
... arguments are passed through to lines, except
lend, which is tightly-controlled in order to keep mid-segment line endings controller but allow the user to override the two ends
if either border or fill are NA, then they just won't be drawn, which would then make one question why using this function
depending on your perspective, dimensions, etc, it is possible that a segment mid-point hints at a little junction, which can be seen as a hint in the left-most segment around (1.8, 3.7) ... this can go away just be resizing, and is I believe just an artifact of raster graphics in general
Related
I've been trying to create a combination of radar/polar chart of a given vector of polygon vertices, without packages, but just with base R, which I really struggle with. So far, with some help, I have reached the following point:
a <- a <- abs(rnorm(5, mean = 4, sd = 2))
names(a) <- LETTERS[1:5]
stars(matrix(a,nrow=1),axes=TRUE, scale=FALSE,col.lines="blue",radius=FALSE)
center <- c(x=2.1, y=2.1) #the starchart for some reason chooses this as a center
half <- seq(0, pi, length.out = 51)
angle=45
for (D in a) {
Xs <- D * cos(half); Ys <- D * sin(half)
lines(center["x"] + Xs, center["y"] + Ys, col = "gray", xpd = NA, lty="dashed")
lines(center["x"] + Xs, center["y"] - Ys, col = "gray", xpd = NA, lty="dashed")
}
which gives me something this:
What I would need to take further is:
center this mixed radar/polar chart at (0,0) and mark the center
color the polygon area transparently
add radii starting from the outermost circle and reaching the center through the polygon vertices
put the vector name labels on the ends of the radii on the outermost circle
So, the final result should look something like this:
I have experimented with the polygon(), symbols() functions and par() graphic parametres, but I am really struggling to combine them...My problem is that I don't understand how the stars() function plot coordinates selection relates to my input.
Did not liked the stars functions... so I made a full rondabout with polygon:
polar_chart <- function(values){
k <- length(values)
m <- max(values)
# initialise plot
plot(1, type="n", xlab="", ylab="", xlim=1.2*m*c(-1,1), ylim=1.2*m*c(-1,1))
# radial lines & letters
sapply(k:1, function(x){
text(1.1*m*cos(-(x-1)*2*pi/k + 2*pi/3), 1.1*m*sin(-(x-1)*2*pi/k + 2*pi/3),
LETTERS[x], cex = 0.75)
lines(c(0, m*cos((x-1)*2*pi/k + 2*pi/3)), c(0, m*sin((x-1)*2*pi/k + 2*pi/3)),
col = "grey",lty="dashed")
})
# circles
aux <- seq(2*pi + 0.1, 0, -0.1)
sapply(values, function(x) lines(x*cos(aux), x*sin(aux), col = "grey",lty="dashed"))
# polygon
x <- values*cos(-(1:k-1)*2*pi/k + 2*pi/3)
y <- values*sin(-(1:k-1)*2*pi/k + 2*pi/3)
polygon(c(x, x[1]),c(y, y[1]), col = "red", border = "blue", density = 50)
}
values <- abs(rnorm(5, mean = 4, sd = 2))
polar_chart(values)
And returns a plot like the following:
Advancing on the answer given here where the same question was asked for a scatter plot, is it possible to plot a line where the colour is based on the y value?
Example data
x = 1:11
y = abs(6 - x)
plot(1:22,c(y,y), col = ifelse(c(y,y) < 2.5, 2, 3), pch = 16)
Will give
However, trying
plot(1:22,c(y,y), col = ifelse(c(y,y) < 2.5, 2, 3), type = "l")
Gives
or doing lines on y<2.5 which gives
instead of the solution I am after, which is
Is there any easy method to do this? This is only a simple case, and I can't manually add each section to my actual data.
Thanks!
Try this
x = 1:11
y = abs(6 - x)
y = c(y,y)
plot(1:22,y, col = ifelse(c(y,y) < 2.5, 2, 3), pch = 16)
for(i in 1:21){
if(y[i]>1.9&& y[i+1]>1.9){
linecolour="green"
} else {
linecolour="red"
}
lines(c((1:22)[i],(1:22)[i+1]),c(y[i],y[i+1]),col=linecolour)
}
Here is a vectorized solution. It is partly based on the answers to this question with the main difference that the function plotted there is a smooth function, unlike this one. This makes a difference when computing the points to plot and their colors.
fun <- function(x) abs(6 - x)
x <- 1:11
y <- fun(x)
X <- c(x, x + 11)
Y <- c(y, y)
n <- length(X)
color <- ifelse((Y[-n] < 2.5) & (Y[-1] < 2.5), 2, 3)
plot(X, Y, col = color, pch = 16)
segments(x0 = X[-n], y0 = Y[-n],
x1 = X[-1], y1 = Y[-1],
col = color, pch = 16)
To remove the points, start with
plot(X, Y, type = "n")
I have data-frame DOTS with following columns: DOT, X, Y. There are 10 dots.
I want to display all possible connections: (a) between dots 1,2,3,4,5; (b) 5,6,7; and (c) between 7,8,9,10?
# what I tried so far
plot(DOTS$X, DOTS$Y, main= "DOTS", xlab= "X", ylab= "Y",
col= "blue", pch = 19, cex = 1, lty = "solid", lwd = 2)
text(DOTS$X, DOTS$Y, labels=DOTS$Dot, cex= 0.7, pos = 3)
lines(DOTS$X,DOTS$Y)
# the last line displays connection from 1 to 2 to 3 etc only
Thank you in advance for your suggestions.
I make a dataset first :
x <- runif(10, 0, 10)
y <- runif(10, 0, 10)
df <- data.frame(dot = LETTERS[1:10], x = x, y = y)
I think it's flexible to create a custom function and use combn() to generate all possible combinations of two dots. And then connect them with segments() respectively. In the custom function below, you can put any dots set and arguments e.g. col, lwd... etc.
plot(df$x, df$y)
text(df$x, df$y, labels = df$dot, pos = 3)
line.fun <- function(index, ...){
comb <- combn(index, 2)
start <- comb[1, ] # starting points
end <- comb[2, ] # end points
segments(df$x[start], df$y[start], df$x[end], df$y[end], ...)
}
line.fun(1:5, col = 2)
line.fun(5:7, col = 3)
line.fun(7:10, col = 4)
Consider the following (derived from signal::filter help page):
require(signal)
bf <- butter(5, 0.1)
t <- seq(0, 1, len = 100)
x <- sin(2*pi*t*2.3) + 0.25*rnorm(length(t)) + 5
y <- filtfilt(bf, x)
z <- filter(bf, x)
plot(t, x, ylim = c(0, max(x)))
lines(t, y, col="red")
lines(t, z, col="blue")
lines(t, stats::filter(x, rep(1/10, 10)), col = "green")
legend("bottomright", legend = c("data", "filtfilt", "filter", "SMA"),
pch = 1, col = c("black", "red", "blue", "green"), bty = "n")
It can be seen that both red and blue (filter and filtfilt, that is) originate in (0,0). However, I would like them to start at a given initial value, such as init = mean(x[1:10]). No matter how I provide said constrain in filter(bf, x), be it integer or vector, I either get no result or length of 'init' must equal length of 'filter'. As a comparison for desired output, a simple moving average (green) is provided. Thank you.
To pass an init value for the blue line, this can be achieved by altering 2 lines of your code.
First: store the filter order in a variable, n
bf <- butter(n<-5, 0.1)
and then create an init vector or matrix of correct size
z <- filter(bf, x, init=rep(mean(x[1:10]), n))
As for the red line, filtfilt is a convenience function that actually does not use init, so if you need to set this for the red line, I think you will want to just call the filter method twice yourself, as done in the source, and pass/handle the init value that way. For example:
filtfilt2 <- function(filt, a, x, init) {
y = filter(filt, a, c(x, numeric(2 * max(length(a), length(filt)))), init=init)
y = rev(filter(filt, a, rev(y)))[seq_along(x)]
y
}
y <- filtfilt2(bf$b, bf$a, x, init=rep(mean(x[1:10]), n))
Is there a way to draw the lines in such a way that they would start on the side of the points, or allow the symbols to be in foreground?
My solution was to make the symbols bigger and more visible.
Edit 1: it's for plot {graphics} of the R program.
Edit 2: the code per popular request.
legend(2,.4,bty='n', c('sugar','citrus','none'), pch=c('s','c','u'), pt.bg='white',lty= c(1,2,3), lwd=1.5, title="Condition",pt.cex=c(1.5),cex=1.5)
Edit 3: This is solved for plot(type='b') but somehow not for legend.
Thanks for reading!
The only thing I can come up with is to manually finagle the dash lengths until they end up looking the way you want them. For instance, this:
> plot(1,1)
> legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty="99", pch=1:2)
produces the image below.
The lty parameter allows you to specify the lengths of lines and dashes as hex characters. In this case, it's saying to create a line of length 9 then create a space of length 9 then repeat. It looks like 9 is about the best fit to space around a normal pch symbol.
Note that you'd probably need to adjust this depending on the size of the image, symbol, etc. My advice ultimately would be to export the image from R and touch up the image to meet your needs in graphic editing software.
Going with the suggestion by #JeffAllen, here is a way to get what I think you might want. It requires modifying the legend() function to return the position of the points (these are given by x1 and y1 in body(legend)[[46]]).
legend2 <- legend
body(legend2)[[49]] <- quote(
invisible(list(rect = list(w = w, h = h, left = left, top = top),
text = list(x = xt, y = yt), points = list(x = x1, y = y1)))
)
Make a plot:
plot(-100:100, -100:100, type = "b")
While drawing the legend, draw white circles (pch = 21 with pt.bg = 'white') over the lines, and assign the values invisibly returned by legend2() to an object. Note also the changes to pt.lwd and pt.cex.
myLegend <- legend2(1, .8, bty = 'n', c('sugar','citrus','none'), pch = 21,
pt.bg = 'white', pt.lwd = 0, lty = c(1, 2, 3), lwd = 1.5, title = "Condition",
pt.cex = c(1.8), cex = 1.5)
Finally, draw the characters you'd like to use in the legend using points(), supplying the x and y values from the object myLegend.
points(myLegend$points$x, myLegend$points$y, pch = c('s','c','u'), cex = 1.5)
And this should get you something like:
You could also use the filled points offered by R (pch=21:25) and specify the fill color using pc.bg which gets passed to the points call when creating a legend.
plot(1,1)
legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty=1, pt.bg=1:2, pch=21:22)
generates the following: