Trajectories are interpolated as smooth curves, and two layers of extra data are plotted through variations in:
1. The thickness of the curve; and
2. The colour of the curve.
A sample trajectory isgenerated as a continuous time random walk, using simm.crw from adehabitat, and converted to a STTDF. Extra layers of data are generated below.
require(spacetime)
require(adehabitatLT)
npts <- 20
rseed <- NA
if (!is.na(rseed)) {
set.seed(rseed)
}
lt <- simm.crw(1:npts, r = 0.7)
sttdf <- as(lt, "STTDF")
xy <- data.frame(coordinates(sttdf))
rseed can be set to particular values. This is useful for reproducing strange plots. I think the only remaining issue, apart from a potential need to fine-tune the shape of the xspline, is the occassional appearance of what might be called “broken corners.” These arise when thick lines are forced around tight corners, and crack apart. This is obviously an internal ggplot problem, because the interpolations are definitively entirely smooth.
Note that xspline has a potentially major issue in that it refers to the aspect ratio of a graphics device and so requires an open device to work. There seems no easy way around this (see here: http://tolstoy.newcastle.edu.au/R/e4/devel/08/02/0403.html ). The workaround here is to open and immediately close a device.
The shape parameter has to be negative to force the spline through the actual points (positive values use them as a hull only). More negative values produce greater curvature.
plot(0)
the.curve <- xspline(xy, shape = -0.5, draw = FALSE)
the.curve <- data.frame(the.curve)
junk <- dev.off(which = dev.cur())
The output of xspline is of arbitrary length (influenced primarily by curvature). In order to represent data through curve colours and widths, the curves need to be regularly interpolated. The following applies a simple linear interpolation to project all points onto a regular number of ninterp between each xy point.
ninterp <- 100
xd <- apply(xy, 1, function(z) {
which.min(sqrt((z[1] - the.curve$x)^2 + (z[2] - the.curve$y)^2))
})
xd <- cbind(xd[1:(npts - 1)], xd[2:npts])
the.curve <- apply(xd, 1, function(z) {
indx <- z[1]:z[2]
data.frame(with(the.curve[indx, ], cbind(x = approx(x, n = ninterp)$y, y = approx(y,
n = ninterp)$y)))
})
the.curve <- do.call(rbind, the.curve)
Two more levels are generated with the following random.curve function, interpolated onto the same resolution as the xspline.
random.curve <- function(npts, len.out, rseed = NA) {
# Generates decently smooth random series for widths and heights
if (!is.na(rseed)) {
set.seed(rseed)
}
findx <- 2:(npts + 1)
y <- as.vector(arima.sim(n = npts + 2, list(ar = c(0.5, 0.4), sd = 1)))
y <- filter(y, filter = rep(1, 3))[findx]
y <- (y - min(y))/diff(range(y))
y <- spline(y, n = len.out, method = "fmm")$y
return(y)
}
thickness <- random.curve(npts, length(the.curve$x), rseed = rseed)
the.curve <- cbind(the.curve, thickness)
col.scale <- random.curve(npts, length(the.curve$x), rseed = rseed)
the.curve <- cbind(the.curve, col.scale)
the.curve is then a data.frame with columns $[x, y, thickness, col.scale]
First define the geometric paths, with the thickness and col.scale defined as part of the aesthetic.
require(ggplot2)
the.plot <- ggplot(the.curve)
aes.def <- aes(x = x, y = y, colour = col.scale, size = thickness)
the.plot <- the.plot + geom_path(aes.def, aes = aes.def)
Then plot the actual colour gradient and thickness, and the finally the original data points, which here are randomly resized, but can naturally also be resized to reflect actual data. The thick.range can obviously be adjusted to suit.
thick.range <- c(0.2, 10)
the.plot <- the.plot + scale_colour_gradient(low = "yellow", high = "lawngreen",
guide = "none")
the.plot <- the.plot + scale_size(range = thick.range, guide = "none")
the.plot <- the.plot + geom_point(data = xy, mapping = aes(x = x, y = y), size = 2 +
4 * runif(npts), pch = 21, colour = "black", fill = "orange")
Then produce the ggplot, removing everything except the line.
new_theme_empty <- theme_bw()
# new_theme_empty$rect <- element_blank() # for blank (white) background
new_theme_empty$panel.background <- element_rect(fill = "gray80", color = NA)
new_theme_empty$panel.border <- element_blank()
new_theme_empty$line <- element_blank()
new_theme_empty$strip.text <- element_blank()
new_theme_empty$axis.text <- element_blank()
new_theme_empty$plot.title <- element_blank()
new_theme_empty$axis.title <- element_blank()
new_theme_empty$plot.margin <- structure(c(0, 0, -1, -1), unit = "lines", valid.unit = 3L,
class = "unit")
the.plot <- the.plot + new_theme_empty
x11()
print(the.plot)
For higher quality output, use:
ggsave("sample_curve.png", the.plot, h = 5, w = 5, type = "cairo-png")