plot a heatmap with a third dimension

14,556

Here's another solution using persp to generate a 3d perspective and then drawing rectangles to generate bars. A lot of lines, but pretty flexible. You need to provide a data matrix (data) and a color matrix ( colmat).

# generate data, random + linear trend in x + linear trend in y
data = matrix(data = runif(n = 100, min = 0, max = 1), nrow=10, ncol = 10, dimnames=list(paste0('x',1:10),paste0('y',1:10)))
data = sweep(x = data, MARGIN = 1, 10:1, FUN = '+')
data = sweep(x = data, MARGIN = 2, 1:10, FUN = '+')

# generate 'empty' persp plot
pmat = persp(x=c(0,10), y=c(0,10), z=matrix(c(0,.1,0,.1), nrow=2), 
             xlim=c(0,10), ylim=c(0,10), zlim=c(0,20), 
             xlab='x', ylab='y', zlab='z', 
             theta=60, phi=20, d=2, box=F) 

# define color ramp
my_cols = heat.colors(10)

# generate color matrix (values between 1 and 10, corresponding to 10 values my_cols
colmat = matrix(data = 1, ncol = 10, nrow = 10)
colmat[1,1:10] <- 5
colmat[5,2:4] <- 8
colmat[6,8] <- 3

# draw each bar: from left to right ...
for (i in 1:nrow(data)){

  # ... and back to front 
  for (j in ncol(data):1){

    xy = which(data == data[i,j], arr.ind=TRUE)

    # side facing y
    x = rep(xy[1],4)
    y = c(xy[2]-1,xy[2],xy[2],xy[2]-1)
    z = c(0,0,data[i,j],data[i,j])
    polygon(trans3d(x, y, z, pmat), col=my_cols[colmat[i,j]], border=1)

    #  side facing x
    x = c(xy[1]-1,xy[1],xy[1],xy[1]-1)
    y = rep(xy[2]-1,4)
    z = c(0,0,data[i,j],data[i,j])
    polygon(trans3d(x, y, z, pmat), col=my_cols[colmat[i,j]], border=1)

    # top side
    x = c(xy[1]-1,xy[1],xy[1],xy[1]-1)
    y = c(xy[2]-1,xy[2]-1,xy[2],xy[2])
    z = rep(data[i,j],4)
    polygon(trans3d(x, y, z, pmat), col=my_cols[colmat[i,j]], border=1)

  }
}

# define axis ranges etc
x.axis <- 1:ncol(data) - 0.5
min.x <- 0
max.x <- 10
y.axis <- 1:nrow(data) - 0.5 
min.y <- 0
max.y <- 10
z.axis <- seq(0, 10, by=10)
min.z <- 0
max.z <- 10

# add some distance between tick labels and the axis
xoffset = 1
yoffset = 0.5
zoffset = 0.5
ticklength = 0.2

# x axis ticks
tick.start <- trans3d(x.axis, min.y, min.z, pmat)
tick.end <- trans3d(x.axis, (min.y - ticklength), min.z, pmat)
segments(tick.start$x, tick.start$y, tick.end$x, tick.end$y)

# y axis ticks
tick.start <- trans3d(max.x, y.axis, min.z, pmat)
tick.end <- trans3d(max.x + ticklength, y.axis, min.z, pmat)
segments(tick.start$x, tick.start$y, tick.end$x, tick.end$y)

# z axis ticks
tick.start <- trans3d(min.x, min.y, z.axis, pmat)
tick.end <- trans3d(min.x, (min.y - ticklength), z.axis, pmat)
segments(tick.start$x, tick.start$y, tick.end$x, tick.end$y)

# x labels
labels <- rownames(data)
label.pos <- trans3d(x.axis, (min.y - xoffset), min.z, pmat)
text(label.pos$x, label.pos$y, labels=labels, adj=c(0, NA), srt=0, cex=0.6)

# y labels
labels <- colnames(data)
label.pos <- trans3d((max.x + yoffset), y.axis, min.z, pmat)
text(label.pos$x, label.pos$y, labels=labels, adj=c(0, NA), srt=0, cex=0.6)

# z labels
labels <- as.character(z.axis)
label.pos <- trans3d(min.x, (min.y - zoffset), z.axis, pmat)
text(label.pos$x, label.pos$y, labels=labels, adj=c(1, NA), srt=0, cex=0.6) 

enter image description here

Share:
14,556
user3419669
Author by

user3419669

Updated on July 25, 2022

Comments

  • user3419669
    user3419669 almost 2 years

    I would like to plot a heatmap like this

    enter image description here

    I know how to do a normal heatmap in R but am unsure how the 3D component can be introduced. I thought about just using a 3d bar chart but then I am not sure how to conditionally set the bar colour. Can somebody recommend a tool to do something like this? Another example would be here

    but there it is not coloured according to the heatmap colours.

    This might go also by the name of 3D histogram. Is there a way to produce such a figure in R (where the hight of the boxes is given by 1 variable and the colour formatting indicated by another ?) like here

    My problem with JTT solution is that I would need to be able to colour the 3D Bars independently of the VA Deaths variable. I have a 2D heatmap (which do already set the colours for each 3D bar). The height of the bar is then set by another variable. This means the colour is not related to the height.

  • user3419669
    user3419669 almost 10 years
    is it somehow possible to label the axes with the row and column names of the data matrix (x an y ) and the z with something like a scale from 0 to 1?
  • user3419669
    user3419669 almost 10 years
    what do you mean with offsets, when I replicate your code I have the labels with the ticks on the lin of the box in which the box plot is drawn as well as additional lines parallel to the x and y and z of the box pic-upload.de/view-24082805/Rplot05.png.html . Is there a way to remove the large box (as in the example you show above). I just re-entered your code ]
  • user3419669
    user3419669 almost 10 years
    in the x .is there a way for the x labels to be not angeled but just to be straight away from the x ? So that they do not overlap with each other
  • koekenbakker
    koekenbakker almost 10 years
    Sorry, my mistake, had to do edits in a hurry. Now axis labels should be fine and box should be gone. Angles can be changed by adjusting the srt argument (String RoTation, I guess). I updated the values so they're all horizontal now. You can change them to any value between 0 and 360 to see what's best.