tab dependent input for shiny dashboard - r

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).

Related

Unable to render plot in shinyapp

I have the code below where I am trying to plot the data from a DF in shiny but the plot box is empty.
What am i doing wrong?
##----------DATA------------##
path <- paste0("C:/WORK/TEMP")
csv_path <- path
daily_data <- read.table(paste0(csv_path,"/file.csv"),
header = T,
sep = ',',
stringsAsFactors = F)
daily_data$COL1 <- as.POSIXct(daily_data$COL1, format = "%m/%d/%Y %H:%M:%S")
str(daily_data)
##----------END DATA------------##
## UI
ui <- dashboardPage( dashboardHeader(title = "Test"), dashboardSidebar(
sidebarMenu(
menuItem("Dashboard", tabName = "dashboard", icon = icon("dashboard"))
) ), dashboardBody(
tabItems(
# First tab content
tabItem(tabName = "dashboard",
fluidRow(
column(width = 5,
box(title = "Plot",
solidHeader = TRUE,
collapsible = TRUE,
width = 12,
plotOutput(outputId="myplot1")
)
)
)
)
) # end tabitems
) # end dashboardbody
) # end dashboardpage
## SERVER
server <- function( input, output ) {
output$myplot1 <- renderPlotly({
ggplotly(
ggplot(daily_data,
aes(
x = COL1,
y = COL3,
color = COL2
)) +
geom_line() +
theme_bw() +
scale_x_datetime(breaks = date_breaks("1 mins")) +
labs(title = "My chart ", x = "Time", y = "%")
)
})
}
shinyApp( ui = ui, server = server )
When I run the App, it runs fine but the plot is empty
When I run the ggplot code alone it comes clean
Thanks to Stefan's comment, the issue is resolved, should be using plotlyOutput instead of plotOutput.

How to change variable name using textInput in a ShinyApp?

I would like the name of the variables to change according to a text typed in textInput.
For example, when I typed "Stack Overflow" in "A1" field, this name ("Stack Overflow") would appear as the new name, instead conj1.
My code:
library(shiny)
library(shinydashboard)
header <- dashboardHeader(title = "Dashboard", titleWidth = 300)
sidebar <- dashboardSidebar(width = 300,
sidebarMenu(
menuItem(text = "Simulador", tabName = "simulador1",icon = icon("dashboard"))
)
)
body <- dashboardBody(
column(id = "c1", width = 12,
textInput(inputId = "ar1", label = "A 1", placeholder = "Digite")
),
column(id = "colsimul4", width = 12,
textInput(inputId = "lvl1", value = 1,label = "Nível 1", placeholder = "Digite")
),
column(width = 12, tableOutput(outputId = "new"))
)
server <- function(session, input, output) {
fpred_1 <- function(x) {
x
}
predattr1 <- reactive({
fpred_1(x = input$ar1)
})
pred_1 <- reactive({
fpred_1(x = input$lvl1)
})
output$new <- renderTable({
isolate(expr = conj1 <- predattr1())
experiment <- expand.grid(conj1 = c(pred_1()))
isolate(expr = experiment)
})
}
ui <- dashboardPage(header, sidebar, body)
shinyApp(ui, server)
I would like the name of the variables conj to be modified according to what is typed in the field A1
I tried this:
isolate(expr = conj1 <- predattr1())
But doesn't work.
For example, if I typed "Stack Overflow", this name appears instead of conj1.
The values ​​change normally, only the variable names do not.
Edit
I tried that too:
output$new <- renderTable({
isolate(expr = conj1 <- predattr1())
x <- names(predattr1())
experiment <- expand.grid(
colnames(x)[1] = c(pred_1())
)
expr = experiment
})
Nothing...
library(shiny)
library(shinydashboard)
################################################################################
# UI
################################################################################
# Header
header <- dashboardHeader(title = "Dashboard", titleWidth = 300)
# Sidebar
sidebar <- dashboardSidebar(width = 300,
sidebarMenu(menuItem(
text = "Simulador",
tabName = "simulador1",
icon = icon("dashboard")
)))
# Body
body <- dashboardBody(
column(
id = "c1",
width = 12,
# Text input 1
textInput(
inputId = "ar1",
label = "A1",
placeholder = "Digite"
)
),
column(
id = "colsimul4",
width = 12,
# Text input 2
textInput(
inputId = "lvl1",
value = 1,
label = "Nível 1",
placeholder = "Digite"
)
),
# Table appears below text inputs in same column/panel
column(width = 12, tableOutput(outputId = "new"))
)
ui <- dashboardPage(header, sidebar, body)
################################################################################
# Server
################################################################################
server <- function(session, input, output) {
# Create table
experiment <- reactive({
df <- expand.grid(req(input$lvl1))
colnames(df) <- req(input$ar1)
return(df)
})
# Render table
output$new <- renderTable({
experiment()
})
}
shinyApp(ui, server)

R Shiny tabpanel tabs with KPI titles

I'd like to have KPI inside R Shiny tabpanel titles, like below, is there an elegant way to do this or a package which can do it? (Please note, the chart is irrelevant to the question).
This is my attempt:
Here is the code:
library(shinydashboard)
ui <- fluidPage({
ib1 <- infoBox("Test 1", 10 * 2, icon = icon("credit-card"), fill = TRUE)
ib2 <- infoBox("Test 2", 10 * 2, icon = icon("table"), fill = TRUE)
tabsetPanel(
tabPanel(ib1, plotOutput("plot")),
tabPanel(ib2)
)
})
server <- function(input, output, session) {
output$plot <- renderPlot({
hist(
rnorm(100),
main = paste("n =", 100),
xlab = ""
)
})
}
shinyApp(ui, server)

Render plotly plots asynchronously in shiny app

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)

Keep plots and input values when switching between tabs

I have a shinydashboard app with two different tab panels. Each tab has different input values and both of them generate a graph when an action button is clicked.
Whenever I switch between these tabs, their respective graphs disappear and input values are reset to default.
I want to keep the tabs in their user modified states (i.e keep both graphs and inputs) even when the user decides to switch between the panels.
Code
library(shiny)
library(shinydashboard)
ui <- fluidPage(
dashboardPage(
dashboardHeader(title = "DASHBOARD"),
dashboardSidebar(
uiOutput("mysidebar"),
),
dashboardBody(
tabsetPanel(type = "tabs", id = "tab",
tabPanel("Tab1", fluid = TRUE, value = 1,plotOutput("A")),
tabPanel("Tab2", fluid = TRUE, value = 2, plotOutput("B"))
)
)
)
)
server <- function(input, output, session){
output$mysidebar <- renderUI({
if(input$tab == 1){
tagList(
sliderInput(inputId = "Sample",
label = "Enter Number of Samples:",
min = 1000, max = 100000,
value = 10000),
fluidRow(
column(6,
actionButton(inputId = "b1", label = "Generate"))
)}
if(input$tab == 2){
tagList(
sliderInput(inputId = "Weight",
label = "Enter Weight:",
value = 100),
fluidRow(
column(6,
actionButton(inputId = "b2", label = "Generate"))
)}
p1<- eventReactive(input$b1, {
#creating a dataframe using input "Sample" in tab1 - Rough example
df <- input$Sample
})
output$SA <- renderPlot({
plot(df)
})
p2 <- eventReactive(input$b2, {
#creating a dataframe using input "Weight" in tab2-- Rough example
df2 <- input$Weight
})
output$A <- renderPlot({
plot(p1())
})
output$B <- renderPlot({
plot(p2())
})
}
I'd much rather you use show and hide functionality within shinyjs package like example below, this way the values will be preserved when you switch between the Tabs
library(shiny)
library(shinyjs)
library(shinydashboard)
ui <- fluidPage(
dashboardPage(
dashboardHeader(title = "DASHBOARD"),
dashboardSidebar(
useShinyjs(),
sliderInput("Sample","Enter Number of Samples:",min = 1000, max = 100000,value = 10000),
sliderInput("Weight","Enter Weight:",min = 1, max = 1000,value = 100),
fluidRow(column(6,actionButton("b1","Generate"),actionButton("b2","Generate")))
),
dashboardBody(
tabsetPanel(type = "tabs", id = "tab",
tabPanel("Tab1", fluid = TRUE, value = 1,plotOutput("A")),
tabPanel("Tab2", fluid = TRUE, value = 2, plotOutput("B"))
)
)
)
)
server <- function(input, output, session){
observe({
if(input$tab == 1){
show("Sample")
show("b1")
hide("Weight")
hide("b2")
}
if(input$tab == 2){
hide("Sample")
hide("b1")
show("Weight")
show("b2")
}
})
p1<- eventReactive(input$b1,{
df <- rnorm(input$Sample)
})
output$SA <- renderPlot({
plot(df)
})
p2 <- eventReactive(input$b2,{
df2 <- rnorm(input$Weight)
})
output$A <- renderPlot({plot(p1())})
output$B <- renderPlot({plot(p2())})
}
shinyApp(ui, server)
The following code keeps the plots and inputs, by using reactiveValues.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(dashboardHeader(title = "DASHBOARD"),
dashboardSidebar(
uiOutput("mysidebar")
),
dashboardBody(
tabsetPanel(type = "tabs", id = "tab",
tabPanel("Tab1", value = 1,plotOutput("SA")),
tabPanel("Tab2", value = 2, plotOutput("SA1"))
)
)
)
server <- function(input, output, session){
slider_react <- reactiveValues(b1=10000, b2 = 100)
observe({
if (input$tab == 1){
output$mysidebar <- renderUI({
tagList(
sliderInput(inputId = "Sample",
label = "Enter Number of Samples:",
min = 1000, max = 100000,
# value = 10000),
value = slider_react$b1),
actionButton(inputId = "b1", label = "Generate"))
})
}
if(input$tab == 2){
output$mysidebar <- renderUI({
tagList(
sliderInput(inputId = "Weight",
label = "Enter Weight:",
min=0, max=1000,
# value = 100),
value = slider_react$b2),
actionButton(inputId = "b2", label = "Generate"))
})
}
})
df_react <- reactiveValues(a1=NULL, a2=NULL)
p1<- observeEvent(input$b1, {
#creating a dataframe using input "Sample" in tab1 - Rough example
df <- runif(input$Sample, 0, 100)
slider_react$b1 = input$Sample
df_react$a1 = df
})
p2 <- observeEvent(input$b2, {
#creating a dataframe using input "Weight" in tab2-- Rough example
df2 <- runif(input$Weight, 0, 100)
slider_react$b2 = input$Weight
df_react$a2 = df2
})
output$SA <- renderPlot({
req(df_react$a1)
plot(df_react$a1)
})
output$SA1 <- renderPlot({
req(df_react$a2)
plot(df_react$a2)
})
}
shinyApp(ui, server)

Resources