I've been attempting to plot lines in either ggplot2 or grid with equal spacing between line segments when the sizes differ. However I've not been succesfull so I ask you for help.
In the examples below, how can I keep the absolute spacing between line segments equal while the line sizes differ?
I'd like to avoid making custom makeContent.myclass
methods to control this myself.
library(ggplot2)
library(grid)
df <- data.frame(
x = c(1:2, 1:2),
y = c(1:2, 2:1),
size = c(1,1,10,10)
)
# In ggplot2
ggplot(df, aes(x, y, size = size, group = size)) +
geom_line(linetype = 2)
# In grid
lines <- polylineGrob(
x = scales::rescale(df$x),
y = scales::rescale(df$y),
id = c(1,1,2,2),
gp = gpar(lty = 2, lwd = c(1, 10))
)
grid.newpage(); grid.draw(lines)
I'd like something that resembles the following made in illustrator. Note that the red line pieces are of equal length.
Any ideas? Thanks for reading!
This is probably not what you're looking for Teunbrand, but I guess you could convert your lines to a series of thin polygonGrobs equally spaced along the lines.
This function takes a series of x and y co-ordinates and returns a dashed line (as a single treeGrob). As per your example it returns it in normalised npc co-ordinates. You have full control over the line width, dash length and break length (though not the pattern), as well as the colour. I'm afraid the units are a bit arbitrary, and this is far from production standard, but it's fairly effective:
segmentify <- function(x, y, linewidth = 1, dash_len = 1,
break_len = 1, col = "black")
{
linewidth <- 0.002 * linewidth
dash_len <- 0.01 * dash_len
break_len <- 0.04 * break_len
if(length(y) != length(x))
stop("x and y must be the same length")
if(!is.numeric(x) | !is.numeric(y))
stop("x and y must be numeric vectors")
if(length(x) < 2)
stop("Insufficient x, y pairs to make line.")
x <- scales::rescale(x)
y <- scales::rescale(y)
n_dashes <- 0
skip_len <- break_len + dash_len
df <- list()
for(i in seq_along(x)[-1])
{
x_diff <- x[i] - x[i - 1]
y_diff <- y[i] - y[i - 1]
seg_len <- sqrt(x_diff^2 + y_diff^2)
seg_prop <- skip_len / seg_len
dist_from_start <- n_dashes * skip_len
prop_start <- dist_from_start/seg_len
x_start <- x[i-1] + prop_start * x_diff
y_len <- y_diff * seg_prop
x_len <- x_diff * seg_prop
y_start <- y[i-1] + prop_start * y_diff
n_breaks <- (seg_len - dist_from_start)/skip_len
n_dashes <- (n_dashes + n_breaks) %% 1
n_breaks <- floor(n_breaks)
if(n_breaks)
{
df[[length( df) + 1]] <- data.frame(
x = seq(x_start, x[i], by = x_len),
y = seq(y_start, y[i], by = y_len)
)
df[[length( df)]]$theta <-
atan(rep(y_diff/x_diff, length( df[[length( df)]]$x)))
}
}
df <- do.call(rbind, df)
df$x1 <- df$x + sin( df$theta) * linewidth + cos(df$theta) * dash_len
df$x2 <- df$x + sin( df$theta) * linewidth - cos(df$theta) * dash_len
df$x3 <- df$x - sin( df$theta) * linewidth - cos(df$theta) * dash_len
df$x4 <- df$x - sin( df$theta) * linewidth + cos(df$theta) * dash_len
df$y1 <- df$y - cos( df$theta) * linewidth + sin(df$theta) * dash_len
df$y2 <- df$y - cos( df$theta) * linewidth - sin(df$theta) * dash_len
df$y3 <- df$y + cos( df$theta) * linewidth - sin(df$theta) * dash_len
df$y4 <- df$y + cos( df$theta) * linewidth + sin(df$theta) * dash_len
do.call(grid::grobTree, lapply(seq(nrow(df)), function(i) {
grid::polygonGrob(c(df$x1[i], df$x2[i], df$x3[i], df$x4[i]),
c(df$y1[i], df$y2[i], df$y3[i], df$y4[i]),
gp = gpar(col = "#00000000", lwd = 0, fill = col))
}))
}
It's fairly straightforward to use:
set.seed(2)
x <- 1:10
y <- rnorm(10)
grid::grid.newpage()
grid::grid.draw(segmentify(x, y))
And changing the line width without affecting the spacing is just like this:
grid::grid.newpage()
grid::grid.draw(segmentify(x, y, linewidth = 3))
And you can control spacing and color like this:
grid::grid.newpage()
grid::grid.draw(segmentify(x, y, linewidth = 2, break_len = 0.5, col = "forestgreen"))