Как сделать график временных рядов с градиентным цветом в R

Как заполнить площадью под и над (sp)линией с градиент цвета?

этот пример был нарисован в Inkscape, но мне нужно вертикальный градиент - не горизонтальная.

интервал ноль to положительное == from белый to красный.

интервал ноль to отрицательный == from белый в красный.

enter image description here

есть пакета который мог это сделать?

я сфабриковал некоторые исходные данные....

set.seed(1)
x<-seq(from = -10, to = 10, by = 0.25)
data <- data.frame(value = sample(x, 25, replace = TRUE), time = 1:25)
plot(data$time, data$value, type = "n")
my.spline <- smooth.spline(data$time, data$value, df = 15)
lines(my.spline$x, my.spline$y, lwd = 2.5, col = "blue")
abline(h = 0)

4 ответов


и вот подход в base R, где мы заполняем всю область участка прямоугольниками градуированного цвета,а затем заполняем обратную область интереса белым.

shade <- function(x, y, col, n=500, xlab='x', ylab='y', ...) {
  # x, y: the x and y coordinates
  # col: a vector of colours (hex, numeric, character), or a colorRampPalette
  # n: the vertical resolution of the gradient
  # ...: further args to plot()
  plot(x, y, type='n', las=1, xlab=xlab, ylab=ylab, ...)
  e <- par('usr')
  height <- diff(e[3:4])/(n-1)
  y_up <- seq(0, e[4], height)
  y_down <- seq(0, e[3], -height)
  ncolor <- max(length(y_up), length(y_down))
  pal <- if(!is.function(col)) colorRampPalette(col)(ncolor) else col(ncolor)
  # plot rectangles to simulate colour gradient
  sapply(seq_len(n),
         function(i) {
           rect(min(x), y_up[i], max(x), y_up[i] + height, col=pal[i], border=NA)
           rect(min(x), y_down[i], max(x), y_down[i] - height, col=pal[i], border=NA)
         })
  # plot white polygons representing the inverse of the area of interest
  polygon(c(min(x), x, max(x), rev(x)),
          c(e[4], ifelse(y > 0, y, 0), 
            rep(e[4], length(y) + 1)), col='white', border=NA)     
  polygon(c(min(x), x, max(x), rev(x)),
          c(e[3], ifelse(y < 0, y, 0), 
            rep(e[3], length(y) + 1)), col='white', border=NA)      
  lines(x, y)
  abline(h=0)
  box()  
}

вот несколько примеров:

xy <- curve(sin, -10, 10, n = 1000)
shade(xy$x, xy$y, c('white', 'blue'), 1000)

pic1

или с цветом определенным палитрой пандуса цвета:

shade(xy$x, xy$y, heat.colors, 1000)

pic2

и применяется к вашим данным, хотя мы сначала интерполируем точки до более тонкого разрешения (если мы не делайте этого, градиент не следует близко за линией, где он пересекает ноль).

xy <- approx(my.spline$x, my.spline$y, n=1000)
shade(xy$x, xy$y, c('white', 'red'), 1000)

pic3


вот один подход, который в значительной степени зависит от нескольких R пространственных пакетов.

основная идея состоит в том, чтобы:

  • построить пустой участок, холст, на который будут уложены последующие элементы. (Выполнение этого первого также позволяет получить пользовательские координаты участка, необходимые в последующих шагах.)

  • используйте векторизованный вызов rect(), чтобы сложить фон мыть цвета. Получение fiddly деталей цветового градиента на самом деле, это самая сложная часть этого.

  • использовать функции топологии в rgeos найти сначала замкнутые прямоугольники в вашей фигуре, а затем их дополнение. Построение дополнения с белой заливкой на фоне мытья покрывает цвет везде за исключением внутри полигонов, только то, что вы хотите.

  • и, наконец, использовать plot(..., add=TRUE), lines(), abline(), etc. чтобы изложить любые другие детали, которые вы как сюжет для показа.


library(sp)
library(rgeos)
library(raster)
library(grid)

## Extract some coordinates
x <- my.spline$x
y <- my.spline$y
hh <- 0
xy <- cbind(x,y)

## Plot an empty plot to make its coordinates available
## for next two sections
plot(data$time, data$value, type = "n", axes=FALSE, xlab="", ylab="")

## Prepare data to be used later by rect to draw the colored background
COL <- colorRampPalette(c("red", "white", "red"))(200)
xx <- par("usr")[1:2]
yy <- c(seq(min(y), hh, length.out=100), seq(hh, max(y), length.out=101))

## Prepare a mask to cover colored background (except within polygons)
## (a) Make SpatialPolygons object from plot's boundaries
EE <- as(extent(par("usr")), "SpatialPolygons")
## (b) Make SpatialPolygons object containing all closed polygons
SL1 <- SpatialLines(list(Lines(Line(xy), "A")))
SL2 <- SpatialLines(list(Lines(Line(cbind(c(0,25),c(0,0))), "B")))
polys <- gPolygonize(gNode(rbind(SL1,SL2)))
## (c) Find their difference
mask <- EE - polys

## Put everything together in a plot
plot(data$time, data$value, type = "n")
rect(xx[1], yy[-201], xx[2], yy[-1], col=COL, border=NA)
plot(mask, col="white", add=TRUE)
abline(h = hh)
plot(polys, border="red", lwd=1.5, add=TRUE)
lines(my.spline$x, my.spline$y, col = "red", lwd = 1.5)

enter image description here


Это ужасный способ обмануть ggplot делать то, что вы хотите. По сути, я делаю гигантскую сетку точек, которые находятся под кривой. Поскольку нет способа установить градиент внутри одного многоугольника, вы должны сделать отдельные многоугольники, следовательно, сетку. Это будет медленно, если вы установите пиксели слишком низко.

gen.bar <- function(x, ymax, ypixel) {
  if (ymax < 0) ypixel <- -abs(ypixel)
  else ypixel <-  abs(ypixel)
  expand.grid(x=x, y=seq(0,ymax, by = ypixel))
}

# data must be in x order.
find.height <- function (x, data.x, data.y) {
  base <- findInterval(x, data.x)
  run <- data.x[base+1] - data.x[base]
  rise <- data.y[base+1] - data.y[base]
  data.y[base] + ((rise/run) * (x - data.x[base]))
}

make.grid.under.curve <- function(data.x, data.y, xpixel, ypixel) {
  desired.points <- sort(unique(c(seq(min(data.x), max(data.x), xpixel), data.x)))
  desired.points <- desired.points[-length(desired.points)]

  heights <- find.height(desired.points, data.x, data.y)
  do.call(rbind, 
          mapply(gen.bar, desired.points, heights, 
                 MoreArgs = list(ypixel), SIMPLIFY=FALSE))
}

xpixel = 0.01
ypixel = 0.01
library(scales)
grid <- make.grid.under.curve(data$time, data$value, xpixel, ypixel)
ggplot(grid, aes(xmin = x, ymin = y, xmax = x+xpixel, ymax = y+ypixel, 
                 fill=abs(y))) + geom_rect() 

цвета не то, что вы хотели, но это, вероятно, слишком медленно для серьезного использования в любом случае.

enter image description here


другая возможность, которая использует функции из grid и gridSVG пакеты.

мы начинаем с генерации дополнительных точек данных с помощью линейной интерполяции в соответствии с методами, описанными @kohske здесь. Затем базовый участок будет состоять из двух отдельных полигонов, один для отрицательных значений и один для положительных значений.

после сюжета была оказана, grid.ls используется для отображения списка grobs, т. е. весь строительный блок участка. В списке мы (среди прочего) найдем два geom_area.polygons; один, представляющий многоугольник для значений <= 0, и один для значений >= 0.

заливка полигона grobs затем манипулируется с помощью gridSVG функции: пользовательские цветовые градиенты создаются с linearGradient, и заливки grobs заменяются с помощью grid.gradientFill.

манипуляции grob градиенты хорошо описаны в главе 7 в дипломная работа Саймона Поттера, одного из авторов .

library(grid)
library(gridSVG)
library(ggplot2)

# create a data frame of spline values
d <- data.frame(x = my.spline$x, y = my.spline$y)

# create interpolated points
d <- d[order(d$x),]
new_d <- do.call("rbind",
                 sapply(1:(nrow(d) -1), function(i){
                   f <- lm(x ~ y, d[i:(i+1), ])
                   if (f$qr$rank < 2) return(NULL)
                   r <- predict(f, newdata = data.frame(y = 0))
                   if(d[i, ]$x < r & r < d[i+1, ]$x)
                     return(data.frame(x = r, y = 0))
                   else return(NULL)
                 })
)

# combine original and interpolated data
d2 <- rbind(d, new_d)
d2  

# set up basic plot
ggplot(data = d2, aes(x = x, y = y)) +
  geom_area(data = subset(d2, y <= 0)) +
  geom_area(data = subset(d2, y >= 0)) +
  geom_line() +
  geom_abline(intercept = 0, slope = 0) +
  theme_bw()

# list the name of grobs and look for relevant polygons
# note that the exact numbers of the grobs may differ
grid.ls()
# GRID.gTableParent.878
# ...
#   panel.3-4-3-4
# ...
#     areas.gTree.834
#       geom_area.polygon.832 <~~ polygon for negative values
#     areas.gTree.838
#       geom_area.polygon.836 <~~ polygon for positive values

# create a linear gradient for negative values, from white to red
col_neg <- linearGradient(col = c("white", "red"),
                          x0 = unit(1, "npc"), x1 = unit(1, "npc"),
                          y0 = unit(1, "npc"), y1 = unit(0, "npc"))

# replace fill of 'negative grob' with a gradient fill
grid.gradientFill("geom_area.polygon.832", col_neg, group = FALSE)

# create a linear gradient for positive values, from white to red
col_pos <- linearGradient(col = c("white", "red"),
                          x0 = unit(1, "npc"), x1 = unit(1, "npc"),
                          y0 = unit(0, "npc"), y1 = unit(1, "npc"))

# replace fill of 'positive grob' with a gradient fill
grid.gradientFill("geom_area.polygon.836", col_pos, group = FALSE)


# generate SVG output
grid.export("myplot.svg")

enter image description here

вы можете легко создавать различные цветовые градиенты для положительных и отрицательных полигонов. Например. если вы хотите, чтобы отрицательные значения выполнялись от белого до синего, замените col_pos выше:

col_pos <- linearGradient(col = c("white", "blue"),
                          x0 = unit(1, "npc"), x1 = unit(1, "npc"),
                          y0 = unit(0, "npc"), y1 = unit(1, "npc"))

enter image description here