Let’s import a volcano in Excalidraw; since there’s no support for raster graphics, we’ll use the {isobands} package to create polygonal contour lines.

The R object

The {isobands} package takes a matrix of elevations and returns a number of contour lines at different heights. Adapting the documentation’s example with the R volcano dataset,

library(minixcali)
library(isoband)

m <- volcano
b <- isobands((1:ncol(m))/(ncol(m)+1), (nrow(m):1)/(nrow(m)+1), m, 10*(9:19), 10*(10:20))
str(b, list.len = 3)
## List of 11
##  $ 90:100 :List of 3
##   ..$ x : num [1:140] 0.984 0.968 0.952 0.935 0.919 ...
##   ..$ y : num [1:140] 0.0114 0.0114 0.0114 0.0114 0.0114 ...
##   ..$ id: int [1:140] 1 1 1 1 1 1 1 1 1 1 ...
##  $ 100:110:List of 3
##   ..$ x : num [1:466] 0.548 0.548 0.532 0.516 0.5 ...
##   ..$ y : num [1:466] 0.0114 0.0114 0.0114 0.0114 0.0114 ...
##   ..$ id: int [1:466] 1 1 1 1 1 1 1 1 1 1 ...
##  $ 110:120:List of 3
##   ..$ x : num [1:560] 0.468 0.468 0.452 0.435 0.435 ...
##   ..$ y : num [1:560] 0.0568 0.0568 0.0682 0.0682 0.0682 ...
##   ..$ id: int [1:560] 1 1 1 1 1 1 1 1 1 1 ...
##   [list output truncated]
##  - attr(*, "class")= chr [1:2] "isobands" "iso"

We have a number of polygons with (x,y) coordinates to pass to xkd_draw() elements, but note that some of these polygons are not simply connected: this will be a problem as Excalidraw will join the pieces that should be disjoint. We’ll therefore break down each path into separate groups using the id attribute returned by isobands().

p <- split(data.frame(b[[2]]), b[[2]]$id)
str(p)
## List of 4
##  $ 1:'data.frame':   262 obs. of  3 variables:
##   ..$ x : num [1:262] 0.548 0.548 0.532 0.516 0.5 ...
##   ..$ y : num [1:262] 0.0114 0.0114 0.0114 0.0114 0.0114 ...
##   ..$ id: int [1:262] 1 1 1 1 1 1 1 1 1 1 ...
##  $ 2:'data.frame':   95 obs. of  3 variables:
##   ..$ x : num [1:95] 0.806 0.823 0.839 0.855 0.871 ...
##   ..$ y : num [1:95] 0.989 0.989 0.989 0.989 0.989 ...
##   ..$ id: int [1:95] 2 2 2 2 2 2 2 2 2 2 ...
##  $ 3:'data.frame':   87 obs. of  3 variables:
##   ..$ x : num [1:87] 0.339 0.323 0.306 0.29 0.29 ...
##   ..$ y : num [1:87] 0.94 0.938 0.932 0.932 0.932 ...
##   ..$ id: int [1:87] 3 3 3 3 3 3 3 3 3 3 ...
##  $ 4:'data.frame':   22 obs. of  3 variables:
##   ..$ x : num [1:22] 0.0161 0.0161 0.0161 0.0161 0.0161 ...
##   ..$ y : num [1:22] 0.511 0.523 0.534 0.545 0.557 ...
##   ..$ id: int [1:22] 4 4 4 4 4 4 4 4 4 4 ...

We now proceed to build the Excalidraw scene one subpath at a time, colouring the levels with both fill and colour.

d <- Excali_doc()

library(scales)

fills <- scales::viridis_pal(alpha = 0.2)(length(b))
cols <- scales::viridis_pal(alpha = 1)(length(b))

scale <- 500

for(ii in seq_along(b)) {
  fill <- fills[ii]
  col <- cols[ii]
  
  l <- b[[ii]]
  spl <- split(data.frame(x = l$x, y = -l$y), l$id)
  
  for (s in spl) {
    r_x <- range(s$x)
    r_y <- range(s$y)
    
    m <- scale * cbind(s$x - r_x[1], s$y - r_y[1])
    
    shape <- xkd_draw(
      x = scale * r_x[1],
      y =  scale * r_y[1],
      width = scale * diff(r_x),
      height = scale * diff(r_y),
      strokeWidth = 0.8,
      roughness = 2L,
      strokeSharpness = "sharp",
      groupIds = list("levelplot", 
                 paste0('level-', ii)),
      fillStyle = "solid",
      strokeColor = col,
      backgroundColor = fill,
      points  = m
    )
    
    d$add(shape)
  }
}

d$export('volcano.json')

You can see this drawing at: https://excalidraw.com/#json=6037869848166400,542JGip6v1DtWfHgYEvG5w