Plot background colour in gradient

后端 未结 3 874
我寻月下人不归
我寻月下人不归 2020-12-01 21:24

This code produces the first plot below:

water.height <- seq(0, 5, 1)
y <- seq(0, 1500, length.out = 6)
df <- data.frame(water.height, y)

library(g         


        
相关标签:
3条回答
  • 2020-12-01 21:50

    The relevant link to the ggplot2 approach was given in the comments. Copied from there:

    library(grid) 
    g <- rasterGrob(blues9, width=unit(1,"npc"), height = unit(1,"npc"), 
    interpolate = TRUE) 
    # grid.draw(g) 
    
    library(ggplot2) 
    ggplot(mtcars, aes(factor(cyl))) + # add gradient background 
       annotation_custom(g, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + 
       geom_bar() # add data layer 
    

    My own approach:

    As usual, I cannot compete with the simple elegance of baptiste's solutions for problems with grid graphics, but here is my approach since I went to all that work:

    gg.background.fill <- function(gg.plot, cols = "white", which = "x") {
      #does not work with facets
    
      stopifnot(which %in% c("x", "y"))
      which1 <- if (which == "x") "width" else "height"
    
      require(gridExtra)
    
      g <- ggplotGrob(gg.plot)
      #g <- ggplotGrob(p)
      gg <- g$grobs      
      findIt <- vapply(gg, function(x) grepl("GRID.gTree", x$name, fixed = TRUE), TRUE)
      n1 <- getGrob(gg[findIt][[1]], "grill.gTree", grep=TRUE)$name
      n2 <- getGrob(gg[findIt][[1]], "panel.background.rect", grep=TRUE)$name
      gg[findIt][[1]]$children[[n1]]$children[[n2]]$gp$fill <- cols
      x <- gg[findIt][[1]]$children[[n1]]$children[[n2]][[which]]
      w <- gg[findIt][[1]]$children[[n1]]$children[[n2]][[which1]]
      attr <- attributes(x)
      x <- seq(0 + c(w)/length(cols)/2, 1 - c(w)/length(cols)/2, length.out = length(cols))
      attributes(x) <- attr
      gg[findIt][[1]]$children[[n1]]$children[[n2]][[which]] <- x
      w <- c(w)/length(cols) 
      attributes(w) <- attr
      gg[findIt][[1]]$children[[n1]]$children[[n2]][[which1]] <- w
      g$grobs <- gg
      class(g) = c("arrange", "ggplot", class(g)) 
      g
    }
    p1 <-  gg.background.fill(p, colorRampPalette(c("red", "blue"))(100))
    print(p1)
    

    resulting plot

    p2 <-  gg.background.fill(p, colorRampPalette(c("red", "blue"))(100), "y")
    print(p2)
    

    enter image description here

    This modifies the existing background which might be considered an advantage, but in contrast to the annotation_custom approach it doesn't work with faceting. More work would be required for that.

    0 讨论(0)
  • 2020-12-01 22:04

    We want to use a linear gradient as the background of a plot.

    Let's start by making a matrix with numbers between 0 and 1.

    # The angle of our linear gradient
    deg <- 45
    rad <- deg / (180 / pi)
    
    # A 5x5 matrix
    n   <- 5
    mat <- matrix(data = 0, ncol = n, nrow = n)
    
    # Let's fill in the matrix.
    for (i in 1:n) {
      for (j in 1:n) {
        mat[i, j] <- (i / n) * cos(rad) + (j / n) * sin(rad)
      }
    }
    

    What did we get?

    mat
    #>           [,1]      [,2]      [,3]      [,4]      [,5]
    #> [1,] 0.2828427 0.4242641 0.5656854 0.7071068 0.8485281
    #> [2,] 0.4242641 0.5656854 0.7071068 0.8485281 0.9899495
    #> [3,] 0.5656854 0.7071068 0.8485281 0.9899495 1.1313708
    #> [4,] 0.7071068 0.8485281 0.9899495 1.1313708 1.2727922
    #> [5,] 0.8485281 0.9899495 1.1313708 1.2727922 1.4142136
    

    That looks pretty close to what we wanted.

    Now, let's clamp the values between 0 and 1.

    mat <- mat - min(mat)
    mat <- mat / max(mat)
    mat
    #>       [,1]  [,2]  [,3]  [,4]  [,5]
    #> [1,] 0.000 0.125 0.250 0.375 0.500
    #> [2,] 0.125 0.250 0.375 0.500 0.625
    #> [3,] 0.250 0.375 0.500 0.625 0.750
    #> [4,] 0.375 0.500 0.625 0.750 0.875
    #> [5,] 0.500 0.625 0.750 0.875 1.000
    

    Much better!

    Let's use grid::rasterGrob() to make a graphical object and draw it.

    library(grid)
    g <- rasterGrob(
      image = mat,
      width = unit(1, "npc"),
      height = unit(1, "npc"), 
      interpolate = TRUE
    )
    grid.newpage()
    grid.draw(g)
    

    Since we have a grob, we can add it to a ggplot2 figure with ggplot2::annotation_custom().

    library(ggplot2)
    
    ggplot(mtcars, aes(factor(cyl))) +
      annotation_custom(
        grob = g, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf
      ) + 
      geom_bar()
    

    Hooray! We did it. But we're not done yet.

    A few notes:

    • It'd be nice to have a function that accepts a few arguments:
      • angle
      • resolution
      • colors to use
    • Our code above is easy to read, but slow to execute. We need it to be faster.

    Please feel free to copy the make_gradient() function below and improve upon it.

    library(ggplot2) 
    library(grid)
    library(RColorBrewer)
    
    make_gradient <- function(deg = 45, n = 100, cols = blues9) {
      cols <- colorRampPalette(cols)(n + 1)
      rad <- deg / (180 / pi)
      mat <- matrix(
        data = rep(seq(0, 1, length.out = n) * cos(rad), n),
        byrow = TRUE,
        ncol = n
      ) +
      matrix(
        data = rep(seq(0, 1, length.out = n) * sin(rad), n),
        byrow = FALSE,
        ncol = n
      )
      mat <- mat - min(mat)
      mat <- mat / max(mat)
      mat <- 1 + mat * n
      mat <- matrix(data = cols[round(mat)], ncol = n)
      grid::rasterGrob(
        image = mat,
        width = unit(1, "npc"),
        height = unit(1, "npc"), 
        interpolate = TRUE
      )
    }
    

    Example 1

    g <- make_gradient(
      deg = 45, n = 500, cols = brewer.pal(9, "Spectral")
    )
    
    ggplot(mtcars, aes(factor(cyl))) +
      annotation_custom(
        grob = g, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf
      ) + 
      geom_bar()
    

    Example 2

    g <- make_gradient(
      deg = 180, n = 500, cols = brewer.pal(9, "RdBu")
    )
    
    ggplot(mtcars, aes(factor(cyl))) +
      annotation_custom(
        grob = g, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf
      ) + 
      geom_bar()
    

    Created on 2019-02-06 by the reprex package (v0.2.1)

    0 讨论(0)
  • 2020-12-01 22:04

    I used Kamil Slowikowski's example to build a simpler function that generates linear gradients depending on a series of values. This can be of use if you have a relationship of some kind between three variables (eg. y~x*z where z also varies over x). Then you just plot y~x and have z~x as a color gradient in the background.

    water.height <- seq(0, 5, 1)
    y <- seq(0, 1500, length.out = 6)
    z <- rnorm(6, 10, 1)
    df <- data.frame(water.height, y, z)
    
    grad_by_val <- function(x, y, cols = blues9) {
      require(grid)
      y <- y[order(x)]
      ys <- (y - min(y)) / diff(range(y))
      cols <- colorRamp(cols)(ys) / 256
      colnames(cols) <- c("red", "green", "blue")
      cols <- apply(cols, 1, function(z) do.call(rgb, as.list(z)))
      mat <- matrix(cols, ncol = length(x))
      rasterGrob(
        image = mat,
        width = unit(1, "npc"),
        height = unit(1, "npc"),
        interpolate = TRUE
      )
    }
    
    library(ggplot2)
    ggplot(df, aes(water.height, y)) + geom_blank() + theme_bw() +
      annotation_custom(
        grob = grad_by_val(df$water.height, df$z),
        xmin = -Inf,
        xmax = Inf,
        ymin = -Inf,
        ymax = Inf
      ) +
      geom_point(
        size = 5,
        color = "#FFFFFF",
        fill = "#000000",
        shape = 21
      )
    

    To add a legend see here.

    0 讨论(0)
提交回复
热议问题