Search code examples
rrglplot3d

Overlapping labels with the plot3D package in R


I am currently creating a 3D scatterplot using the plot3D package in R, and I wanted to add data labels to my data points. However, some of my data points have the same values with each other, and I wanted to find a solution similar to ggrepel that would offset the data labels from the points so that the labels for those points would be legible. Sample code below:

library(plot3D)
names <- c("A", "B", "C", "D", "E")
x <- c(1,1,2,3,4)
y <- c(1,1,3,4,5)
z <- c(1,1,4,5,6)

scatter3D(x, y, z)
text3D(x,y,z, names, add = TRUE, cex = 1)

The labels for A and B are currently superimposed on top of each other.

I tried to use the directlabels package as well, and it doesn't seem to recognize the text3D or the plot3D objects. Any help would appreciated.


Solution

  • The plotrix::thigmophobe function works out directions to try to stop labels from overlapping in 2D plots. rgl doesn't have anything equivalent, and since you can rotate a plot, you'll always be able to rotate labels on top of each other. However, the function below makes an attempt to place labels for one particular view so that they don't overlap.

    thigmophobe.text3d <- function(x, y = NULL, z = NULL, texts, ...) {
      xyz <- xyz.coords(x, y, z)
    
      # Get the coordinates as columns in a matrix in
      # homogeneous coordinates
      pts3d <- rbind(xyz$x, xyz$y, xyz$z, 1)
    
      # Apply the viewing transformations and convert 
      # back to Euclidean
      pts2d <- asEuclidean(t(par3d("projMatrix") %*% 
                             par3d("modelMatrix") %*% 
                             pts3d))
    
      # Find directions so that the projections don't overlap
      pos <- plotrix::thigmophobe(pts2d)
    
      # Set adjustments for the 4 possible directions
      adjs <- matrix(c(0.5, 1.2,   
                       1.2, 0.5,  
                       0.5, -0.2,  
                      -0.2, 0.5), 
                     4, 2, byrow = TRUE)
    
      # Plot labels one at a time in appropriate directions.
      for (i in seq_along(xyz$x)) 
        text3d(pts3d[1:3, i], texts = texts[i], 
               adj = adjs[pos[i],], ...)
    }
    

    There are some problems with the function above: it is based on rgl::text3d rather than plot3D::text3D, so the optional arguments are different; it plots labels one at a time, which can be inefficient if you have a lot of labels, it doesn't do error checking, etc.

    Edited to add:

    The unreleased version 0.99.20 of rgl adds a thigmophobe3d function to do this. You'll have to get it from https://r-forge.r-project.org/R/?group_id=234 or the Github mirror https://github.com/rforge/rgl for now.