Home > Net >  Run JavaScript on browser, and process the results on server using R and Shiny
Run JavaScript on browser, and process the results on server using R and Shiny

Time:09-28

I am building a Shiny app involving simulation. The simulation itself takes up some resources, and it becomes a problem - a burden to the server - if the application is used by many users.

So, I was thinking of that hey, why not write the simulation part in JavaScript, run it in a browser, and then take the results to R. These results can be then processed on the server, plots can be drawn, etc.

Is this even possible?

Ok, let's illustrate this with a simple example: Here is a JavaScript code that creates a list of numbers: 0, 3, 6, 9, 12, 15, 18, 21, 24, 27 ...and which should be run on the browser:

let numberlist = [];
for (let i = 0; i < 10; i  ) {
  numberlist.push(i*3);
}

Now, if I want to plot this in Shiny, the UI. R file would probably look like this:

## ui.R ##
fluidPage(
  sidebarLayout(
    sidebarPanel(
    #Sidebar panel can be empty, I think.
    ),
    mainPanel(plotOutput("Plot"))
  )
)

But what about the server.R?

## server.R ##
function(input, output) {
  output$Plot <- renderPlot({
    #What should I put here to:
    #plot(c(0, 3, 6, 9, 12, 15, 18, 21, 24, 27))
    #i.e. to plot(c(JavaScript result))
  })
}

Is this even the way to do this, or should the results from the JavaScript code be taken to the server using the UI somehow?

CodePudding user response:

For the case you describe, you can use Shiny.setInputValue. Create a subfolder www of your app folder, and put in this subfolder the following JavaScript file, say it is named myjs.js:

$(document).on("shiny:connected", function () {
  let numberlist = [];
  for (let i = 0; i < 10; i  ) {
    numberlist.push(i * 3);
  }
  Shiny.setInputValue("numbers", numberlist);
});

Include this file in ui.R:

ui <- fluidPage(
  tags$head(tags$script(src = "myjs.js")),
  ......
)

Then in your server.R file, you will receive the list of numbers in input[["numbers"]]. It will be a list, not an atomic vector, so you have to use unlist:

plot(unlist(input[["numbers"]]))

Now, say you want to set the length of your list of numbers in R, and send this length to JS. You need a custom message handler in this case:

$(document).on("shiny:connected", function () {
  Shiny.addCustomMessageHandler("getNumbers", function (l) {
    let numberlist = [];
    for (let i = 0; i < l; i  ) {
      numberlist.push(i * 3);
    }
    Shiny.setInputValue("numbers", numberlist);
  });
});

Here is a scenario example:

## ui.R ##
fluidPage(
  tags$head(tags$script(src = "myjs.js")),
  sidebarLayout(
    sidebarPanel(
      numericInput("length", "Length", min = 10, max = 100, value = 10, step = 1)
    ),
    mainPanel(plotOutput("Plot"))
  )
)

## server.R ##
server <- function(input, output, session){

  observeEvent(input[["length"]], {
    session$sendCustomMessage("getNumbers", input[["length"]])
  })

  output[["Plot"]] <- renderPlot({
    req(input[["numbers"]])
    plot(unlist(input[["numbers"]]))
  })

}
  • Related