R shinydashboard: Mix of dynamic and static tabItems for various menusubitems - r

I am building an app with three segments:
Overview
Detailed results
Help
The detailed result section should show results of many sub-items, one at the time.
I am interested to the Result section to be a single tab, because I don't want to write code each tab for each sub-item. Each sub-item has identical, in the example a histogram.
When I run the example though, I loose the ID of the subitems.
Is it possible to have a layout like this but to keep the ID's of all menuitems and menusubitems?
Happy to look at alternative approaches.
An example to illustrate the issue is below. The solution will show the table in overview, a histogram in results for any of the sub-items and the HTML output in the help section.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
sidebarMenu(id = "SideBarMENU",
menuItem("Overview", tabName = "OVERVIEW", selected = TRUE),
menuItem("Results", startExpanded = TRUE,
menuSubItem("Sepal.Length", tabName = "RESULTS"),
menuSubItem("Sepal.Width" , tabName = "RESULTS"),
menuSubItem("Petal.Length", tabName = "RESULTS"),
menuSubItem("Petal.Width" , tabName = "RESULTS")
),
menuItem("Help", tabName = "HELP")
)
),
dashboardBody(
tabItems(
tabItem("OVERVIEW",
box("Overview box",
tableOutput("overview"))
),
tabItem("RESULTS",
box("Results box",
plotOutput("results")
)
),
tabItem("HELP",
box("HELP box",
textOutput("help"))
)
)
)
)
server <- function(input, output, session) {
data <- reactive({
print(input$SideBarMENU)
if(input$SideBarMENU %in% names(iris)){
iris[[input$SideBarMENU]]
} else {
rnorm(100, 1000, 10)
}
})
output$results <- renderPlot({
hist(data())
})
output$overview <- renderTable({
head(iris)
})
output$help <- renderText({
HTML("A wiki is a website on which users collaboratively.....")
})
}
shinyApp(ui, server)

Basically, you need two components:
Dynamic content / plots
Dynamic dashboard body
The first part is more easy:
1. Dynamic content / plots
You can create the outputs in a loop as explained in a few other SO posts:
lapply(nms, function(name){
output[[name]] <- renderUI ({
box("Results Box", plotOutput(paste0("plot_", name)))
})
output[[paste0("plot_", name)]] <- renderPlot({
hist(iris[[input$SideBarMENU]], main = "")
})
})
2. Dynamic dashboard body
This part is more complicated. You need dynamic tabitems() and they have to be mixed with static parts. In order to hand over a list of tabitem() to tabitems() you can use do.call(tabItems, ..) for converting it, see the link below. To combine them with the static elements, convert the static ones as list() elements and combine them all in a list() before calling do.call(tabItems, ..).
output$tabItms <- renderUI ({
itemsDyn <- lapply(nms, function(name){
tabItem(tabName = name, uiOutput(name))
})
items <- c(
list(
tabItem("OVERVIEW",
box("Overview box",
tableOutput("overview"))
)
),
itemsDyn,
list(
tabItem("HELP",
box("HELP box",
textOutput("help"))
)
)
)
do.call(tabItems, items)
})
Similar components can be found here: shinydashboard does not work with uiOutput
and for looping tabItems() here: How to make a function in a for loop or lapply loop in a tabItem dashboard shiny.
Note:
I modify names(iris):
nms <- gsub("[.]", "", names(iris))
names(iris) <- nms
because no dots are allowed for the tabItem names.
Reproducible example:
library(shiny)
library(shinydashboard)
nms <- gsub("[.]", "", names(iris))
names(iris) <- nms
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
uiOutput("menu")
),
dashboardBody(
uiOutput("tabItms")
)
)
server <- function(input, output, session) {
output$tabItms <- renderUI ({
itemsDyn <- lapply(nms, function(name){
tabItem(tabName = name, uiOutput(name))
})
items <- c(
list(
tabItem("OVERVIEW",
box("Overview box",
tableOutput("overview"))
)
),
itemsDyn,
list(
tabItem("HELP",
box("HELP box",
textOutput("help"))
)
)
)
do.call(tabItems, items)
})
lapply(nms, function(name){
output[[name]] <- renderUI ({
box("Results Box", plotOutput(paste0("plot_", name)))
})
output[[paste0("plot_", name)]] <- renderPlot({
hist(iris[[input$SideBarMENU]], main = "")
})
})
output$menu <- renderUI({
sidebarMenu(id = "SideBarMENU",
menuItem("Overview", tabName = "OVERVIEW", selected = TRUE),
menuItem("Results", id = "resultChoice", startExpanded = TRUE,
lapply(nms, function(name) {
menuSubItem(name, tabName = name)
})
),
menuItem("Help", tabName = "HELP")
)
})
output$overview <- renderTable({
head(iris)
})
output$help <- renderText({
HTML("A wiki is a website on which users collaboratively.....")
})
}
shinyApp(ui, server)

Related

shiny: better way to create tables in loop across tab panels

I have to create a Shiny/ShinyDashboard app which basically creates a bunch of tables for various teams. Users will select their team from the sidebar and then they will have several tab panels to choose from depending on the data. See here:
Now the requirement is that I have to split the data for each tab panel into distinct datatables and -because of the data- I have to generate this dynamically.
I came up with the following code (reprex down here) but since I'm quite new to Shiny, I wondered if:
I could split UI and data code even more
there is frankly a better way to do this
library(shiny)
library(shinydashboard)
library(datasets)
library(dplyr)
library(DT)
cars <- mtcars
irises <- iris
cars$team <- sample(c("Team1", "Team2"), nrow(cars), replace = TRUE)
irises$team <-
sample(c("Team1", "Team2"), nrow(irises), replace = TRUE)
# UI
ui <- dashboardPage(
dashboardHeader(title = "Teams"),
dashboardSidebar(sidebarMenu(
menuItem("Team 1",
tabName = "tab_team1",
icon = icon("dashboard")),
menuItem("Team 2",
tabName = "tab_team2",
icon = icon("dashboard"))
)),
dashboardBody(tabItems(
tabItem(tabName = "tab_team1",
fluidRow(
tabBox(
title = "",
width = "100%",
tabPanel(title = "A",
uiOutput("Team1_content_A")),
tabPanel(title = "B",
uiOutput("Team1_content_B"))
)
)),
tabItem(tabName = "tab_team2",
fluidRow(
tabBox(
title = "",
width = "100%",
tabPanel(title = "A",
uiOutput("Team2_content_A")),
tabPanel(title = "B",
uiOutput("Team2_content_B"))
)
))
))
)
server <- function(input, output, session) {
lapply(1:2, function(i) {
t <- paste0("Team", i)
table <- cars %>%
filter(team == t)
output[[paste0(t, "_content_A")]] <- renderUI({
lapply(sort(unique(table$gear)), function(i) {
id <- paste0(t, "_content_A_", i)
output[[id]] <-
DT::renderDataTable(datatable(table[table$gear == i, ]))
fluidRow(
box(
width = "100%",
title = paste0("Gears: ", i),
status = "info",
solidHeader = TRUE,
collapsible = TRUE,
DT::dataTableOutput(id)
)
)
})
})
table2 <- irises %>%
filter(team == t)
output[[paste0(t, "_content_B")]] <- renderUI({
lapply(sort(unique(table2$Species)), function(i) {
id <- paste0(t, "_content_B_", i)
output[[id]] <-
DT::renderDataTable(datatable(table2[table2$Species == i, ]))
fluidRow(
box(
width = "100%",
title = paste0("Species: ", i),
status = "info",
solidHeader = TRUE,
collapsible = TRUE,
DT::dataTableOutput(id)
)
)
})
})
})
}
shinyApp(ui, server)
Echo to #Limey, I would also suggest to use shiny modules https://mastering-shiny.org/scaling-modules.html. There are two reasons.
Reduce unnecessary computation. Currently the computation is run for all the four panels (team1_tabA, team1_tabB, team2_tabA, team2_tabB) at the same time. Ideally, as you add more features or data in the future, you would want to only run the necessary computation when certain action is performed. (i.e. when user click team1_tabA, only the required tables is calculated, no need to calculate tables for other tabs.). Modules can help achieve it.
More flexible control over UI and Server. Currently your app has the same server function and outputs for all the four panels, it works for now. But if in the future you want the four panels to have different layout and outputs, the current coding style might prompt you to write more complex and repeated code. And modules can help you get rid of the repeat and help with more flexible control over the UI and server.
Here is a modularized version of your shiny app. I encountered some issues with using namespace (NS(id)) in the dynamic UI (renderUI), and thanks to the feedback from #YBS Why the shiny dynamic UI + modules does not give the desired output?, the problem is solved, and the modularized shiny is able to run.
## module UI
tab_ui <- function(id) {
ns <- NS(id) ## namespace function
uiOutput(ns("content"))
}
## module Server
tab_server <- function(id, data, Team, var) {
moduleServer(id, function(input, output, session) {
ns <- session$ns ## call namespace in the server
table <- reactive({
data %>% filter(team == Team)
})
output$content <- renderUI({
lapply(sort(unique(table()[[var]])), function(i) {
idd <- paste0("content_", i)
output[[idd]] <-
DT::renderDataTable(datatable(table()[table()[[var]] == i, ]))
fluidRow(
box(
width = "100%",
title = paste0(var, " ", i),
status = "info",
solidHeader = TRUE,
collapsible = TRUE,
DT::dataTableOutput(ns(idd)) ## !!! need to use namespace
)
)
})
})
})
}
## library
library(shiny)
library(shinydashboard)
library(datasets)
library(dplyr)
library(DT)
## data
cars <- mtcars
irises <- iris
cars$team <- sample(c("Team1", "Team2"), nrow(cars), replace = TRUE)
irises$team <-
sample(c("Team1", "Team2"), nrow(irises), replace = TRUE)
## UI
ui <- dashboardPage(
dashboardHeader(title = "Teams"),
dashboardSidebar(sidebarMenu(
menuItem("Team 1",
tabName = "tab_team1"
),
menuItem("Team 2",
tabName = "tab_team2"
)
)),
dashboardBody(tabItems(
tabItem(
tabName = "tab_team1",
fluidRow(
tabBox(
title = "",
width = "100%",
tabPanel(
title = "A",
tab_ui("team1_tabA") ## module ui
),
tabPanel(
title = "B",
tab_ui("team1_tabB") ## module ui
)
)
)
),
tabItem(
tabName = "tab_team2",
fluidRow(
tabBox(
title = "",
width = "100%",
tabPanel(
title = "A",
tab_ui("team2_tabA") ## module ui
),
tabPanel(
title = "B",
tab_ui("team2_tabB") ## module ui
)
)
)
)
))
)
## server
server <- function(input, output, session) {
# module server
tab_server("team1_tabA", data = cars, Team = "Team1", var = "gear")
tab_server("team1_tabB", data = irises, Team = "Team1", var = "Species")
tab_server("team2_tabA", data = cars, Team = "Team2", var = "gear")
tab_server("team2_tabB", data = irises, Team = "Team2", var = "Species")
}
shinyApp(ui, server)

Empty the search bar of a datatable by default instead of including the highlighted text

Is there a way to make the Search bar of the datatable empty instead of having the 'setosa' inside it by default while keeping the 'setosa' highlighted inside the table? Or at least find another way to highlight or underline the 'setosa'?
library(DT)
ui <- dashboardPage(
dashboardHeader(title = "Dynamic sidebar"),
dashboardSidebar(
),
dashboardBody(
DT::dataTableOutput("t")
)
)
server <- function(input, output) {
output$t <- renderDT(
datatable(iris, options = list(searchHighlight = TRUE, search = list(search = 'setosa')))
)
}
shinyApp(ui, server)
Ok, you can do something like this.
library(DT)
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(title = "Dynamic sidebar"),
dashboardSidebar(
),
dashboardBody(
DT::dataTableOutput("t")
)
)
server <- function(input, output) {
data <- reactive({
mydata <- iris
rownames(mydata) <- gsub("setosa",tags$span(style="color:red", "setosa"),rownames(mydata))
for(i in 1:ncol(mydata)){
mydata[,i] <- gsub("setosa",tags$span(style="color:red", "setosa"),mydata[,i])
}
mydata
})
output$t <- renderDT(
datatable(data(), options = list(searchHighlight = TRUE, search = list(search = '')), escape = F)
)
}
shinyApp(ui, server)

How to display reactive number of tables at once (with different results)? Shiny App

I have a problem with displaying reactive number of tables depending on my selection. What I'd like to do is to render as many tables as it's unique records in one column and display them one by one with results for each subgroup.
It'll be easier if I share this example:
dashboardHeader(title = "My App"),
dashboardSidebar(id="", sidebarMenu(
menuItem(strong("Tab1"), tabName = "T1", icon = icon("table")))),
dashboardBody(
tabItems(
tabItem(
tabName="T1",
fluidRow(tableOutput('tables'))
)
)
)
)
server <- function(input, output) {
observeEvent(length(unique(mtcars$gear)), {
lapply(1:length(unique(mtcars$gear)),
function(i){
output[[paste0('table', i)]]<-renderTable({
filtered<-mtcars
a<-list()
for (j in unique(filtered$gear)){
subd <- filtered[filtered$gear == j,]
a[[j]]<-subd
}
for(i in 1:length(a)){
a[[i]]
}
a[[3]]
})
})
})
output$tables <- renderUI({
lapply(1:length(unique(mtcars$gear)),
function(i) {
uiOutput(paste0('table', i))
})
})
}
shinyApp(ui = ui, server = server)
What I'd like to get out of this code is to have 3 tables (one with summary table for gear=3, one for gear=4 and one for gear=5). What I'm getting right now is 3 tables with results for gear=3.
I've tried to write a for loop which goes through the list but I don't know where I supposed to add this for loop to make it work correctly.
Thanks!
You can use split to make a list of tables based on the levels of a factor within the data frame. From there, slight modifications of your renderUI should get you the desired results. Note also that I changed the tables output to uiOutput instead of tableOutput because you populate that with renderUI.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(title = "My App"),
dashboardSidebar(id = "", sidebarMenu(
menuItem(strong("Tab1"), tabName = "T1", icon = icon("table"))
)),
dashboardBody(tabItems(tabItem(
tabName = "T1",
fluidRow(uiOutput('tables'))
)))
)
server <- function(input, output) {
tables_in_list <- split(x = mtcars, f = mtcars$gear)
output$tables <- renderUI({
lapply(seq_along(tables_in_list), function(i)
renderTable(tables_in_list[[i]]))
})
}
shinyApp(ui = ui, server = server)

How to share dataframes from an observeEvent module to another

I need to share more than one dataframe within an observeEvent block with other observeEvent blocks. The reason is because the data is built only after a button is pressed.
I found the following two questions very resourceful, but not quite close to the structure of my app ...
How to return a variable from a module to the server in an R Shiny app?
How to access dataframe from another observeEvent?
I tried to wrap the button observeEvent within a module, but then the app does not work. I cannot figure out how to change my code into modules to make it work.
Here is a minimal example.
library(shiny)
library(shinydashboard)
library(DT)
header1 <- dashboardHeader(
title = "My App"
)
sidebar1 <- dashboardSidebar(
sidebarMenu(id = "sbmenu",
menuItemOutput("menuitems01"),
menuItemOutput("menuitems02")
) #sidebarMenu
) #dashboardSidebar
body1 <- dashboardBody(
tabItems(
uiOutput("tabitems01")
) #tabItems
) #dashboardBody
ui <- dashboardPage(header1, sidebar1, body1)
server <- function(input, output, session) {
# render menu
output$menuitems01 <- renderMenu({
menuItem("Main", tabName = "main", icon = icon("key"))
})
# render tabitems
output$tabitems01 <- renderUI({
tabItem(tabName = "main",
h2("Main"),
actionButton(inputId = "btn1", label = "Button1")
) #tabItem
}) #renderUI
observeEvent(input$btn1, {
dfresult02 <- data.frame(c(1, 2), c(3, 4)) # e.g. read some data from db
dfresult05 <- data.frame(c(5, 6), c(7, 8)) # e.g. read some data from db
rResult02 <- reactive({dfresult02}) # NEED TO MAKE THIS DATA AVAILABLE TO OTHER MODULE(S)
rResult05 <- reactive({dfresult05}) # NEED TO MAKE THIS DATA AVAILABLE TO OTHER MODULE(S)
output$menuitems02 <- renderMenu({
menuItem("MyData", tabName = "mydata", icon = icon("th"))
}) #renderMenu
updateTabItems(session, "sbmenu", "mydata")
print("button1 pressed")
}) #observeEvent(input$btn1)
observeEvent(input$sbmenu, {
# IF I UNCOMMENT THE NEXT FOUR LINES, THE TABLES ARE DISPLAYED
#dfresult02 <- data.frame(c(1, 2), c(3, 4))
#rResult02 <- reactive({dfresult02})
#dfresult05 <- data.frame(c(1, 2), c(3, 4))
#rResult05 <- reactive({dfresult05})
if(input$sbmenu == "mydata")
{
output$tabitems01 <- renderUI({
tabItem(tabName = "mydata",
h2("My Data"),
DT::dataTableOutput('tbl02'),
DT::dataTableOutput('tbl05')
) #tabItem
}) #renderUI
output$tbl02 <- DT::renderDataTable({rResult02()}) # NEED DATA FROM OTHER MODULE HERE
output$tbl05 <- DT::renderDataTable({rResult05()}) # NEED DATA FROM OTHER MODULE HERE
} #if(input$sbmenu == "mydata")
if(input$sbmenu == "main")
{
output$tabitems01 <- renderUI({
tabItem(tabName = "main",
h2("Main"),
actionButton(inputId = "btn1", label = "Button1")
) #tabItem
}) #renderUI
} #if(input$sbmenu == "main")
}) #observeEvent(input$sbmenu)
} #server
shinyApp(ui = ui, server = server)
Using the very useful comment above, I ended up with this code, that works perfectly! Thank you so much!!! (note the use of reactiveValues)
library(shiny)
library(shinydashboard)
library(DT)
header1 <- dashboardHeader(
title = "My App"
)
sidebar1 <- dashboardSidebar(
sidebarMenu(id = "sbmenu",
menuItemOutput("menuitems01"),
menuItemOutput("menuitems02")
) #sidebarMenu
) #dashboardSidebar
body1 <- dashboardBody(
tabItems(
uiOutput("tabitems01")
) #tabItems
) #dashboardBody
ui <- dashboardPage(header1, sidebar1, body1)
server <- function(input, output, session) {
# DECLARE REACTIVEVALUES FUNCTION HERE
rResult <- reactiveValues(df02 = 0, df05 = 0)
# render menu
output$menuitems01 <- renderMenu({
menuItem("Main", tabName = "main", icon = icon("key"))
})
# render tabitems
output$tabitems01 <- renderUI({
tabItem(tabName = "main",
h2("Main"),
actionButton(inputId = "btn1", label = "Button1")
) #tabItem
}) #renderUI
observeEvent(input$btn1, {
dfresult02 <- data.frame(c(1, 2), c(3, 4)) # e.g. read some data from db
dfresult05 <- data.frame(c(5, 6), c(7, 8)) # e.g. read some data from db
rResult$df02 <- dfresult02 # MAKE THIS DATA AVAILABLE TO OTHER MODULE(S)
rResult$df05 <- dfresult05 # MAKE THIS DATA AVAILABLE TO OTHER MODULE(S)
output$menuitems02 <- renderMenu({
menuItem("MyData", tabName = "mydata", icon = icon("th"))
}) #renderMenu
updateTabItems(session, "sbmenu", "mydata")
print("button1 pressed")
}) #observeEvent(input$btn1)
observeEvent(input$sbmenu, {
if(input$sbmenu == "mydata")
{
output$tabitems01 <- renderUI({
tabItem(tabName = "mydata",
h2("My Data"),
DT::dataTableOutput('tbl02'),
DT::dataTableOutput('tbl05')
) #tabItem
}) #renderUI
output$tbl02 <- DT::renderDataTable(rResult$df02) # GET DATA FROM OTHER MODULE(S) HERE
output$tbl05 <- DT::renderDataTable(rResult$df05) # GET DATA FROM OTHER MODULE(S) HERE
} #if(input$sbmenu == "mydata")
if(input$sbmenu == "main")
{
output$tabitems01 <- renderUI({
tabItem(tabName = "main",
h2("Main"),
actionButton(inputId = "btn1", label = "Button1")
) #tabItem
}) #renderUI
} #if(input$sbmenu == "main")
}) #observeEvent(input$sbmenu)
} #server
shinyApp(ui = ui, server = server)

R Shinydashboard dynamic menuItem

I'm trying to generate multiple menuItems dynamically, may be simple, but I'm not getting the right idea.
library(shiny)
library(shinydashboard)
port_tables<-c("tab1","tab2","tab3","tab4") # These are from a DB connection in the original code
function(input, output) {
output$smenu1 <- renderMenu({
sidebarMenu( id = "tabs",
h4("Tables",style="color:yellow;margin-left:20px;"),
paste("menuItem(\"",port_tables,"\",tabName=\"",port_tables,"\",icon=icon('th'))",collapse=",")
)
})
)
The menuItems from the paste function doesn't resolve( I get the result of paste function on the sidebar). I tried eval, eval(parse(paste(...))), both didn't work - what am I missing?
I couldn't quite make out what you're asking for, but here's an example of something with a dynamic menu.
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(title = "Dynamic Menu"),
dashboardSidebar(
sidebarMenuOutput(outputId = "dy_menu")
),
dashboardBody(
tabItems(
tabItem(tabName = "main",
textInput(inputId = "new_menu_name",
label = "New Menu Name"),
actionButton(inputId = "add",
label = "Add Menu")
)
)
)
)
server <- function(input, output, session){
output$dy_menu <- renderMenu({
menu_list <- list(
menuItem("Add Menu Items", tabName = "main", selected = TRUE),
menu_vals$menu_list)
sidebarMenu(.list = menu_list)
})
menu_vals = reactiveValues(menu_list = NULL)
observeEvent(eventExpr = input$add,
handlerExpr = {
menu_vals$menu_list[[length(menu_vals$menu_list) + 1]] <- menuItem(input$new_menu_name,
tabName = input$new_menu_name)
})
}
shinyApp(ui, server)
I changed the code as follows and it worked :
library(shiny)
library(shinydashboard)
port_tables<-c("tab1","tab2","tab3","tab4") # These are from a DB connection in the original code
text1<-paste("menuItem(\"",port_tables,"\",tabName=\"",port_tables,"\",icon=icon('th'))")
text2<-paste("sidebarMenu(id = 'tabs',textInput('port', 'Enter port:'),h4('Tables',style='color:yellow;margin-left:20px;'),",paste(text1,collapse=","),paste(")"))
function(input, output) {
output$smenu1 <- renderMenu({
eval(parse(text=text2))
})
)
So, the key is put the whole content of sidebarMenu in a text field and evaluate it

Resources