Cassie Nitty
Cassie Nitty

Reputation: 43

How to rotate 3D Plotly continuous for R shiny App

I am trying to create a constant rotating 3D scatter plotly so that I can put it in my R shiny app. However, I can't seem to get it to constantly rotate (like this: https://codepen.io/etpinard/pen/mBVVyE). I don't want to save it to an image/gif just directly use in my App. Can anyone provide any help to get it continuously rotating (I have little experience with Python)? I've tried this in the Viewer screen of R studio, but it doesn't rotate there.

library(plotly)
library(ggplot2)


N <- 100

x <- rnorm(N, mean = 50, sd = 2.3)
y <- runif(N,min= 0, max = 100)
z <- runif(N, min = 4, max = 70)
luci.frame <- data.frame(x,y,z)




for (i in seq(0,100, by=0.1)){
  cam.zoom = 2
  ver.angle = 0
  graph <- plot_ly()%>%
    add_trace(type = "scatter3d", 
              mode = "markers", 
              data = luci.frame,
              x = ~x, 
              y = ~y, 
              z = ~z) %>%
    layout(scene = list(
      camera = list(
        eye = list(
          x = cos(i)*cam.zoom,
          y = sin(i)*cam.zoom, 
          z = 0.3
        ), 
        center = list(
          x = 0, 
          y = 0, 
          z = 0
        )
        
      )
      
      
    )
    )
  graph
  
}

I am very new to plotly, so any help would be greatly appreciated.

Upvotes: 3

Views: 2040

Answers (1)

ismirsehregal
ismirsehregal

Reputation: 33530

We can reuse most of the JS code via htmlwidgets::onRender. You tagged the question - wrapped it in an app accordingly:

library(shiny)
library(plotly)
library(htmlwidgets)

ui <- fluidPage(
  plotlyOutput("graph")
)

server <- function(input, output, session) {
  N <- 100
  x <- rnorm(N, mean = 50, sd = 2.3)
  y <- runif(N, min = 0, max = 100)
  z <- runif(N, min = 4, max = 70)
  luci.frame <- data.frame(x, y, z)
  
  output$graph <- renderPlotly({
    plot_ly(
      type = "scatter3d",
      mode = "markers",
      data = luci.frame,
      x = ~ x,
      y = ~ y,
      z = ~ z
    ) %>%
      layout(scene = list(camera = list(
        eye = list(
          x = 1.25,
          y = 1.25,
          z = 1.25
        ),
        center = list(x = 0,
                      y = 0,
                      z = 0)
      ))) %>%
      onRender("
      function(el, x){
  var id = el.getAttribute('id');
  var gd = document.getElementById(id);
  Plotly.update(id).then(attach);
  function attach() {
    var cnt = 0;
    
    function run() {
      rotate('scene', Math.PI / 180);
      requestAnimationFrame(run);
    } 
    run();
    
    function rotate(id, angle) {
      var eye0 = gd.layout[id].camera.eye
      var rtz = xyz2rtz(eye0);
      rtz.t += angle;
      
      var eye1 = rtz2xyz(rtz);
      Plotly.relayout(gd, id + '.camera.eye', eye1)
    }
    
    function xyz2rtz(xyz) {
      return {
        r: Math.sqrt(xyz.x * xyz.x + xyz.y * xyz.y),
        t: Math.atan2(xyz.y, xyz.x),
        z: xyz.z
      };
    }
    
    function rtz2xyz(rtz) {
      return {
        x: rtz.r * Math.cos(rtz.t),
        y: rtz.r * Math.sin(rtz.t),
        z: rtz.z
      };
    }
  };
}
    ")
  })
}

shinyApp(ui, server)

result1


The same can be done via plotlyProxy without additional JS - but it's not as smooth:

library(shiny)
library(plotly)

ui <- fluidPage(
  plotlyOutput("graph")
)

server <- function(input, output, session) {
  N <- 100
  x <- rnorm(N, mean = 50, sd = 2.3)
  y <- runif(N, min = 0, max = 100)
  z <- runif(N, min = 4, max = 70)
  luci.frame <- data.frame(x, y, z)
  
  mySequence <- seq(0, 100, by = 0.1)
  
  cam.zoom = 2
  # ver.angle = 0
  
  output$graph <- renderPlotly({
    plot_ly(
      type = "scatter3d",
      mode = "markers",
      data = luci.frame,
      x = ~ x,
      y = ~ y,
      z = ~ z
    ) %>%
      layout(scene = list(camera = list(
        eye = list(
          x = cos(mySequence[1]) * cam.zoom,
          y = sin(mySequence[1]) * cam.zoom,
          z = 0.3
        ),
        center = list(x = 0,
                      y = 0,
                      z = 0)
      )))
  })
  
  myPlotlyProxy <- plotlyProxy("graph")
  count <- reactiveVal(1L)
  
  observe({
    invalidateLater(100)
    plotlyProxyInvoke(myPlotlyProxy, "relayout", list(scene = list(camera = list(
      eye = list(
        x = cos(mySequence[isolate(count())]) * cam.zoom,
        y = sin(mySequence[isolate(count())]) * cam.zoom,
        z = 0.3
      ),
      center = list(x = 0,
                    y = 0,
                    z = 0)
    ))))
    
    isolate(count(count()+1))
    
    if(count() > length(mySequence)){
      count(1L)  
    }
  })
}

shinyApp(ui, server)

result2

Upvotes: 3

Related Questions