Render plotly plots asynchronously in shiny app - r

In shiny app I render couple plotly plots at once, but they render only after all of them are calculated. For example, if rendering 8 of 9 plots takes 8 seconds and rendering 9th takes 15 seconds, the first 8 plots will appear only after 9th is rendered (after 15 seconds instead of 8). See example below.
box_plot1 appears only when box_plot2 is rendered. I played a bit with shiny promises, but didn't find solution so far.
MWE:
library(shinydashboard)
library(plotly)
header <- dashboardHeader(
title = ""
)
body <- dashboardBody(
fluidRow(
column(width = 6,
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot1")
)
),
column(width = 6,
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot2")
)
)
)
)
ui <- dashboardPage(
header,
dashboardSidebar(disable = TRUE),
body
)
server <- function(input, output, session) {
output$box_plot1 <- plotly::renderPlotly({
p <- plot_ly(ggplot2::diamonds, x = ~cut, y = ~price, color = ~clarity, type = "box") %>%
layout(boxmode = "group")
p
})
output$box_plot2 <- plotly::renderPlotly({
for (i in 1:3) {
print(i)
Sys.sleep(1)
}
plot_ly(ggplot2::diamonds, y = ~price, color = ~cut, type = "box")
})
}
shinyApp(ui = ui, server = server)

The answer by #DSGym works in showing one plot after another but this still does not function asynchronously. In fact if you have a plot which takes a long time to render or a data frame which take a long time to calculate, we need to perform these operations asynchronously. As an example consider this regular shiny app with no asynchronous support,
library(shinydashboard)
library(plotly)
library(future)
library(promises)
plan(multisession)
header <- dashboardHeader(
title = ""
)
body <- dashboardBody(
fluidRow(
column(width = 6,
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot1")
)
),
column(width = 6,
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot2")
)
)
)
)
ui <- dashboardPage(
header,
dashboardSidebar(disable = TRUE),
body
)
server <- function(input, output, session) {
output$box_plot1 <- plotly::renderPlotly({
for (i in 1:10) {
print(i)
Sys.sleep(1)
}
plot_ly(ggplot2::diamonds, x = ~cut, y = ~price, color = ~clarity, type = "box") %>%
layout(boxmode = "group")
})
output$box_plot2 <- plotly::renderPlotly({
for (i in 11:20) {
print(i)
Sys.sleep(1)
}
plot_ly(ggplot2::diamonds, y = ~price, color = ~cut, type = "box")
})
}
shinyApp(ui = ui, server = server)
Each plot counts to 10 and shows its output. The whole operation takes 20+ seconds to complete from when runApp() is executed.
To call both the plots asynchronously we use the futures and promises package.
library(shinydashboard)
library(plotly)
library(future)
library(promises)
plan(multisession)
header <- dashboardHeader(
title = ""
)
body <- dashboardBody(
fluidRow(
column(width = 6,
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot1")
)
),
column(width = 6,
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot2")
)
)
)
)
ui <- dashboardPage(
header,
dashboardSidebar(disable = TRUE),
body
)
server <- function(input, output, session) {
output$box_plot1 <- plotly::renderPlotly({
future({
for (i in 1:10) {
print(i)
Sys.sleep(1)
}
plot_ly(ggplot2::diamonds, x = ~cut, y = ~price, color = ~clarity, type = "box") %>%
layout(boxmode = "group")
})
})
output$box_plot2 <- plotly::renderPlotly({
future({
for (i in 11:20) {
print(i)
Sys.sleep(1)
}
plot_ly(ggplot2::diamonds, y = ~price, color = ~cut, type = "box")
})
})
}
shinyApp(ui = ui, server = server)
Now, even though both plots count up to 10, the plots execute asynchronously. The total time to load the plots reduced to below 20 seconds.
However, both plots still load together. This is because of the inherent flush cycle in shiny. Hence, even if we execute the plots asynchronously, all plots will always load at the same time.
You can read more about this here: https://rstudio.github.io/promises/articles/shiny.html

You can use renderUI in combination with reactiveValues which keep track of the order of the calculations.
library(shinydashboard)
library(plotly)
header <- dashboardHeader(
title = ""
)
body <- dashboardBody(
fluidRow(
column(width = 6,
uiOutput("plot1")
),
column(width = 6,
uiOutput("plot2")
)
)
)
ui <- dashboardPage(
header,
dashboardSidebar(disable = TRUE),
body
)
server <- function(input, output, session) {
rv <- reactiveValues(val = 0)
output$plot1 <- renderUI({
output$box_plot1 <- plotly::renderPlotly({
for (i in 3:5) {
print(i)
Sys.sleep(1)
}
p <- plot_ly(ggplot2::diamonds, x = ~cut, y = ~price, color = ~clarity, type = "box") %>%
layout(boxmode = "group")
rv$val <- 1
p
})
return(
tagList(
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot1")
)
)
)
})
output$plot2 <- renderUI({
if(rv$val == 0) {
return(NULL)
}
output$box_plot2 <- plotly::renderPlotly({
for (i in 1:3) {
print(i)
Sys.sleep(1)
}
plot_ly(ggplot2::diamonds, y = ~price, color = ~cut, type = "box")
})
return(
tagList(
box(width = NULL, solidHeader = TRUE,
plotly::plotlyOutput("box_plot2")
)
)
)
})
}
shinyApp(ui = ui, server = server)

Related

Creation and deletion of dynamic boxes in R Shiny using shinydashboardplus package (boxDropdownItem)

I'm trying to create a page to include and exclude boxes dynamically using boxDropdownItem from shinydashboardplus package, but the application is crashing, could someone help me please?
*Solutions using javascript are also welcome :)
Here my code:
shinyApp(
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
uiOutput("boxes")
)
),
server = function(input, output) {
rvs = reactiveValues(boxDropdownItem = list(), observers = list())
output$boxes <- renderUI({
for(i in 1:5) {
rvs$boxDropdownItem[[i]] =
column(width = 12,
box( id = paste("box",i),
title = paste("box",i),
width = 4,
status = NULL,
dropdownMenu = boxDropdown(
icon = icon("ellipsis-v"),
boxDropdownItem(id = paste0("del",i), "Delete")
)
)
)
}
rvs$observers = lapply(1:(length(rvs$boxDropdownItem)),function(i) {
observeEvent(input[[paste0("del",i)]],{
rvs$observers <- rvs$observers[-i]
rvs$boxDropdownItem <- rvs$boxDropdownItem[-i]
})
})
do.call(fluidRow, rvs$boxDropdownItem)
})
}
)
You need to first create the boxes as a reactiveValues object. Then you can control what you display in renderUI. I have shown here for 3 boxes. You can modify it to dynamic number. Try this
library(shinydashboardPlus)
shinyApp(
ui = shinydashboard::dashboardPage(title = "My Box Dropdown",
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
uiOutput("boxes")
)
),
server = function(input, output) {
rvs = reactiveValues(boxDropdownItem = list(), observers = list(), tmp=list())
observe({
for(i in 1:3) {
rvs$boxDropdownItem[[i]] <-
box(id = paste0("box",i),
title = paste("box",i),
width = 12,
status = "warning",
solidHeader = TRUE,
collapsible = TRUE,
dropdownMenu = boxDropdown(
icon = icon("ellipsis-v"),
boxDropdownItem("Click me", id = paste0("dropdownItem",i), icon = icon("heart")),
dropdownDivider(),
boxDropdownItem(id = paste0("del",i), "Delete")
),
paste("My Box",i)
)
}
})
output$boxes <- renderUI({
if (length(rvs$tmp)>0){
rvs$boxDropdownItem[!(rvs$boxDropdownItem %in% rvs$tmp)]
} else rvs$boxDropdownItem
})
lapply(1:3, function(i) {
observeEvent(input[[paste0("del",i)]],{
rvs$tmp[[i]] <<- rvs$boxDropdownItem[[i]]
}, ignoreInit = TRUE)
observeEvent(input[[paste0("dropdownItem",i)]], {
showNotification("Hello", duration = i, type = "error")
})
})
}
)
The picture below shows box 2 deleted.

Module inside module shiny

I'm trying to call a module from inside a module and having some problems.
This first code is working, it displays an app with a button that creates a popup. Inside the popup is a plot and a slider input. The popup-plot is defined in it's own module.
library(shiny)
library(shinyWidgets)
uiForModal <<- function(id) {
ns <- NS(id)
tagList(
fluidRow(
plotOutput(outputId = ns("plot")),
sliderInput(
inputId =ns( "clusters"),
label = "Number of clusters",
min = 2, max = 6, value = 3, width = "100%"
)
)
)
}
serverForModal <<- function(input, output, session) {
output$plot <- renderPlot({
print(head(iris))
plot(Sepal.Width ~ Sepal.Length,
data = iris, col = Species,
pch = 20, cex = 2)
points(kmeans(iris[, 1:2], input$clusters)$centers,
pch = 4, cex = 4, lwd = 4)
})
}
ui <- fluidPage(
actionButton("showPlot", "showPlot")
)
server <- function(input, output){
observeEvent(input$showPlot, {
show_alert(
title = "Some Title",
text = tags$div(
uiForModal("test1")
),
html = TRUE,
width = "80%"
)
})
callModule(serverForModal, "test1")
}
runApp(shinyApp(ui, server))
The problem occurs when I try to put the button inside its own module. The code below is my failed attempt at this. I think the problem is something to do with the namespace. In the code below, the button calls the UI with the popup and slider, but the plot doesn't show. So I think the problem is in the server namespace for the plot. Can someone please help me out?
library(shiny)
library(shinyWidgets)
uiForModal <<- function(id) {
print(id)
ns <- NS(id)
print(ns("plot"))
tagList(
fluidRow(
plotOutput(outputId = ns("plot")),
sliderInput(
inputId =ns( "clusters"),
label = "Number of clusters",
min = 2, max = 6, value = 3, width = "100%"
)
)
)
}
serverForModal <<- function(input, output, session) {
output$plot <- renderPlot({
print(head(iris))
plot(Sepal.Width ~ Sepal.Length,
data = iris, col = Species,
pch = 20, cex = 2)
points(kmeans(iris[, 1:2], input$clusters)$centers,
pch = 4, cex = 4, lwd = 4)
})
}
uiForButton <<- function(id) {
ns <- NS(id)
tagList(
fluidRow(
actionButton(ns("showPlot"), "showPlot")
)
)
}
serverForButton <<- function(input, output, session, ns) {
observeEvent(input$showPlot, {
show_alert(
title = "Some Title",
text = tags$div(
uiForModal(ns("test2"))
),
html = TRUE,
width = "80%"
)
})
callModule(serverForModal, ns("test2"))
}
ui <- fluidPage(
uiForButton("test1")
)
server <- function(input, output){
callModule(serverForButton, "test1", NS("test1"))
}
runApp(shinyApp(ui, server))
Change
callModule(serverForModal, ns("test2"))
to
callModule(serverForModal, "test2")

Passing a reactive value to a plotting function as argument

I am trying to pass a reactive value derived from the selectizeInput select1 to a plotting function. The reactive value itself is dependent on input from awesomeRadio radio1.
Unfortunately, passing this value to the plot function does not work, although it's accessible by renderText(). Here is an example:
library(shiny)
library(shinydashboard)
##### plot function ------------------------
plot_function <- function(highlight){
plot(1:9)
points(x=c(1:9)[highlight],
y=c(1:9)[highlight],
col="red")
}
##### ui ------------------------
header <- dashboardHeader(
title = "Title",
titleWidth = 500
)
body <- dashboardBody(
fluidRow(
column(width = 4,
box(title = "Plot 1",
plotOutput("plot1", height = 300)
),
box(width = NULL, status = "warning",
awesomeRadio(inputId = "radio1",
label = "Radio1",
choices = c("Option1","Option2"),
inline = TRUE,
status="primary"
),br(),
uiOutput("select1"))
),
column(width = 3,
box(width = NULL, status = "warning",
textOutput(outputId = "option1")),
box(width = NULL, status = "warning",
textOutput(outputId = "option2")))
)
)
ui = dashboardPage(
header,
dashboardSidebar(disable = TRUE),
body
)
##### server ------------------------
server = function(input, output) {
onSessionEnded(stopApp)
choices = reactive({
if (input$radio1=="Option1") {
1:4
} else if (input$radio1=="Option2") {
5:8
}
})
output$select1 <- renderUI({
selectizeInput("select1", label="Select1",
choices=choices())
})
tmp = reactive({input$radio1})
tmp2 = reactive({input$select1})
output$plot1 <- renderPlot(plot_function(highlight = tmp2()))
output$option1 = renderText(paste(tmp()))
output$option2 = renderText(paste(tmp2()))
}
##### ui ------------------------
shinyApp(ui,server)
Also, I don't quite understand the different behavior of tmp and tmp2, since passing tmp to the plot function would work.
Thank you for any help!
After few changes in your code
library(shiny)
library(shinyWidgets)
ui <- fluidPage(
fluidRow(
column(width = 4,
box(title = "Plot 1",
plotOutput("plot1", height = 300)
),
box(width = NULL, status = "warning",
awesomeRadio(inputId = "radio1",
label = "Radio1",
choices = c("Option1","Option2"),
inline = TRUE,
status = "primary"
),br(),
uiOutput("select1"))
),
column(width = 3,
box(width = NULL, status = "warning",
textOutput(outputId = "option1")),
box(width = NULL, status = "warning",
textOutput(outputId = "option2")))
)
)
##### server ------------------------
server <- function(input, output) {
onSessionEnded(stopApp)
choices <- reactive({
if (input$radio1 == "Option1") {
1:4
} else if (input$radio1 == "Option2") {
5:8
}
})
output$select1 <- renderUI({
selectizeInput("sel", label = "Select1",
choices = choices())
})
output$plot1 <- renderPlot({
plot(1:9)
points(x = c(1:9)[as.numeric(input$sel)],
y = c(1:9)[as.numeric(input$sel)],
col = "red")
})
output$option1 <- renderText(input$radio1)
output$option2 <- renderText(input$sel)
}
shinyApp(ui,server)

Cannot show the text from data.frame using renderText in leaflet R Shiny

I have a dataframe with points. I want to show text from column clicking on the marker. Lets assume I have next code:
library(tidyverse)
library(leaflet)
df <- data.frame(X = c(30.45, 30.66), Y = c(24.14, 25.23), id = c(1, 2),
description = c("First point is here", "Second point is here"))
server <- function(input, output) {
data_of_click <- reactiveValues(clickedMarker=NULL)
output$map <- renderLeaflet({
leaflet() %>%
addTiles(options = providerTileOptions(noWrap = TRUE)) %>%
addCircleMarkers(data = df,
lng = ~ X, lat = ~ Y, radius = 3,
color = "black", fillColor = "red", stroke = F,
fillOpacity = 0.5,
layerId = df$id
)
})
observeEvent(input$map_marker_click, {
data_of_click$clickedMarker <- input$map_marker_click
})
output$text <- renderText({
if (is.null(data_of_click$clickedMarker)) {
return(NULL)
}
return(
paste0(df$description,
id == data_of_click$clickedMarker$id
)
)
})
}
ui <- fluidPage(
br(),
column(8, leafletOutput("map", height = "600px")),
column(4, br(), br(), br(), br(), textOutput("text")),
br()
)
shinyApp(ui = ui, server = server)
I adapted this part from previous answer where I needed to create table on click. Now I want to make simple printed text based on column description.

tab dependent input for shiny dashboard

I am facing an issue with shiny dashboard. I am trying to create a simple dashboard with two tabItems on the left. Each tabItem have their specific set of controls and a plot. But I am probably missing something on the server side to link the input to the tab because the controls of the second tab is behaving strangely. Any help would be much appreciated. Here is my code
library(shiny)
library(shinydashboard)
library(data.table)
library(ggplot2)
data = data.table(group = rep(c(1, 3, 6), each = 10), x = rep(1:10, times = 3), value = rnorm(30))
sidebar <- dashboardSidebar(
sidebarMenu(id = 'sidebarMenu',
menuItem("tab 1", tabName = "tab1", icon = icon("dashboard")),
menuItem("tab 2", icon = icon("th"), tabName = "tab2")
)
)
body <- dashboardBody(
tabItems(
tabItem(tabName = "tab1",
fluidRow(
box(title = "Controls",
checkboxGroupInput('group', 'group:', c(1, 3, 6), selected = 6, inline = TRUE), width = 4),
box(plotOutput("plot1"), width = 8)
)
),
tabItem(tabName = "tab2",
fluidRow(
box(title = "Controls",
checkboxGroupInput('group', 'group:', c(1, 3, 6), selected = 6, inline = TRUE), width = 4),
box(plotOutput("plot2"), width = 8)
)
)
)
)
# Put them together into a dashboardPage
ui <- dashboardPage(
dashboardHeader(title = "test tabbed inputs"),
sidebar,
body,
skin = 'green'
)
server <- function(input, output) {
output$plot1 <- renderPlot({
plotData <- data[group %in% input$group]
p <- ggplot(plotData, aes(x = x, y = value, colour = factor(group))) + geom_line() + geom_point()
print(p)
})
output$plot2 <- renderPlot({
plotData <- data[group %in% input$group]
p <- ggplot(plotData, aes(x = x, y = value, colour = factor(group))) + geom_line() + geom_point()
print(p)
})
}
shinyApp(ui, server)
When I change input in the first tab it also changes in the second and then when I try to change it back often time nothing happens or it just behaves weirdly. I think I need to specify tie the input to the tabItems somehow but could not find a good example of doing that. Any help would be much appreciated.
Thanks,
Ashin
To deal with a dynamic number of tabs or other widgets, create them in server.R with renderUI. Use a list to store the tabs and the do.call function to apply the tabItems function. The same for the sidebar.
I think my code below generates your expectation.
library(shiny)
library(shinydashboard)
library(data.table)
library(ggplot2)
data = data.table(group = rep(c(1, 3, 6), each = 10), x = rep(1:10, times = 3), value = rnorm(30))
sidebar <- dashboardSidebar(
uiOutput("Sidebar")
)
body <- dashboardBody(
uiOutput("TABUI")
)
# Put them together into a dashboardPage
ui <- dashboardPage(
dashboardHeader(title = "test tabbed inputs"),
sidebar,
body,
skin = 'green'
)
server <- function(input, output) {
ntabs <- 3
tabnames <- paste0("tab", 1:ntabs) # "tab1", "tab2", ...
checkboxnames <- paste0(tabnames, 'group') # "tab1group", "tab2group", ...
plotnames <- paste0("plot", 1:ntabs) # "plot1", "plot2", ...
output$Sidebar <- renderUI({
Menus <- vector("list", ntabs)
for(i in 1:ntabs){
Menus[[i]] <- menuItem(tabnames[i], tabName = tabnames[i], icon = icon("dashboard"), selected = i==1)
}
do.call(function(...) sidebarMenu(id = 'sidebarMenu', ...), Menus)
})
output$TABUI <- renderUI({
Tabs <- vector("list", ntabs)
for(i in 1:ntabs){
Tabs[[i]] <- tabItem(tabName = tabnames[i],
fluidRow(
box(title = "Controls",
checkboxGroupInput(checkboxnames[i], 'group:', c(1, 3, 6), selected = 6, inline = TRUE),
width = 4),
box(plotOutput(paste0("plot",i)), width = 8)
)
)
}
do.call(tabItems, Tabs)
})
RV <- reactiveValues()
observe({
selection <- input[[paste0(input$sidebarMenu, 'group')]]
RV$plotData <- data[group %in% selection]
})
for(i in 1:ntabs){
output[[plotnames[i]]] <- renderPlot({
plotData <- RV$plotData
p <- ggplot(plotData, aes(x = x, y = value, colour = factor(group))) +
geom_line() + geom_point()
print(p)
})
}
}
shinyApp(ui, server)
Note that I put the "plot data" in a reactive list. Otherwise, if I did that:
output[[plotnames[i]]] <- renderPlot({
selection <- input[[paste0(input$sidebarMenu, 'group')]]
plotData <- data[group %in% selection]
...
the plot would be reactive each time you go back to a tab (try to see what I mean).

Resources