rplotplotlyscatter-plotscatter3d

How to center the axes in Plotly on a 3d scatter plot in R?


I have a 3d scatter plot that I created with Plotly in R - is there anyway to move the axes to the middle? My plot right now looks similar to this plot that I made real quick in R:

enter image description here

But I would like to remove the grid background, the axis ticks, and move the axes to the middle of the plot to make it look similar to this:

enter image description here

My main problem is moving the axes to the middle while maintaining the x, y, and z labels on them. I have used traces to simulate the central axes, but then I have the issue of no axis labels when I remove the background grid and axes. What is the best way to go about this?

The code to recreate the first plot is below as well:

coords = list("x"=c(), "y"=c(), "z"=c())
for(phi in seq(0, 2*pi, 0.2)) {
  for(theta in seq(0, pi, 0.2)) {
    x = (8 * sin(theta) * cos(phi))
    y = (8 * sin(theta) * sin(phi))
    z = (8 * cos(theta))
    coords$x = append(coords$x, x)
    coords$y = append(coords$y, y)
    coords$z = append(coords$z, z)
  }
}
df = data.frame("x"=coords$x, "y"=coords$y, "z"=coords$z)
fig = plot_ly(df, x=~x, y=~y, z=~z, type="scatter3d",
              mode="markers", marker=list(size=3))
fig = layout(fig, scene=list(xaxis=list(range=c(-12, 12)),
                             yaxis=list(range=c(-12, 12)),
                             zaxis=list(range=c(-12, 12))))
fig

Solution

  • How about this?

    enter image description here

    If you make the text bold: enter image description here

    I think it would look better if I could make the text bold. In the object annots that will be made in the function getvals. Where you see text = "x" (y or z), if you prefer bold, annotate like text = "<b>x</b>".

    I tried to make this dynamic so that it could be more readily repurposed, but I didn't test any Plotly possible extremes. It is based on the assumption that there is only 1 trace and that that trace is a scatter3d.

    I want to point out that marking the lines as mode = "lines" was continuously trumped by Plotly, rendering as lines+markers. If I didn't designate it as lines+markers, I couldn't control the markers, either. That's why you see markers called but essentially hidden.

    getvals <- function(plt) {
      plt <- plotly_build(plt) # ensure data is in object
      if(isTRUE(length(plt$x$data) == 1)) {
        df1 <- data.frame(x = plt$x$data[[1]]$x, # extract data
                          y = plt$x$data[[1]]$y,
                          z = plt$x$data[[1]]$z)
        mx <- max(df1$x); my <- max(df1$y); mz <- max(df1$z) # for segments
        nx <-.25 * mx; ny <- .25 * my; nz <- .25 * mz # for line segment size
        d <- colMeans(df1)
        dx <- d[[1]]; dy <- d[[2]]; dz <- d[[3]]     # line segment center
        hx <- dx + nx; hy <- dy + ny; hz <- dz + nz  # line segment high
        lx <- dx - nx; ly <- dy - ny; lz <- dz - nz  # line segment low
        plt <- plt %>%  # add the traces for the internal axes indic.
          add_trace(x = c(lx, hx), y = c(dy, dy), z = c(dz, dz), # x axis line
                    mode = "lines+markers", 
                    hoverinfo = "skip", marker = list(size = .01),
                    line = list(color = "black", width = 3)) %>% 
          add_trace(x = c(dx, dx), y = c(ly, hy), z = c(dz, dz), # y axis line
                    mode = "lines+markers",
                    hoverinfo = "skip", marker = list(size = .01),
                    line = list(color = "black", width = 3)) %>% 
          add_trace(x = c(dx, dx), y = c(dy, dy), z = c(lz, hz), # z axis line
                    mode = "lines+markers", 
                    hoverinfo = "skip", marker = list(size = .01),
                    line = list(color = "black", width = 3)) %>% 
          add_trace(x = dx, y = dy, z = dz, type = "scattered",  # center ball
                    mode = "markers", hoverinfo = "skip", 
                    marker = list(size = 8, color = "black"))
        annots <- list(                   # text annotations x, y, z
          list(showarrow = F, x = hx, y = dy, z = dz,
               text = "x", xanchor = "right", xshift = -5,
               font = list(size = 20)),
          list(showarrow = F, x = dx, y = hy, z = dz,
               text = "y", xshift = -5, 
               font = list(size = 20)),
          list(showarrow = F, x = dx, y = dy, z = hz,
               text = "z", xshift = -5,
               font = list(size = 20)))
        assign("annots", annots, envir = .GlobalEnv)
        plt # return plot; send annotations to the global env
      }
    }
    

    Using that function

    Take your original plot, less the call for layout. I added showlegend = F, because when the other traces are added, it would have created a legend. You could pipe this to the plot, or do it like this.

    fig = plot_ly(df, x = ~x, y = ~y, z = ~z, type = "scatter3d",
                  mode = "markers", marker = list(size = 3),
                  showlegend = F) 
    
    fig %>% getvals() %>% 
      layout(
        scene = list(
          aspectratio = list(x = 1, y = 1, z = 1),
          camera = list(
            center = list(x = 0, y = 0, z = 0),
            eye = list(x = -.5, y = .5, z = .6)),
          up = list(x = 0, y = 0, z = 1),
          xaxis = list(showgrid = FALSE, zeroline = FALSE, range = c(-12, 12),
                       showticklabels = FALSE, title = list(text = "")),
          yaxis = list(showgrid = FALSE, zeroline = FALSE, range = c(-12, 12),
                       showticklabels = FALSE, title = list(text = "")),
          zaxis = list(showgrid = FALSE, zeroline = FALSE, range = c(-12, 12),
                       showticklabels = FALSE, title = list(text = "")),
        dragmode = "turntable",
        annotations = annots
      ), margin = list(t = 30, r = 30, l = 30, b = 30, padding = 2))