Hello, I'm new to Shiny and also to this forum. I'm trying to create an app with different pages (one page for a different dataset). Each page would have a tab for a data table and a tab for some plots. I am successful in making it work if it was only one page:
#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
# Load necessary libraries
library(shiny)
library(DT)
library(ggplot2)
library(scales)
# Load the data
data <- read.csv("universities.csv", stringsAsFactors = FALSE)
ui_home <- fluidPage(
titlePanel("Welcome to the BC Post-Secondary School Employee Salaries App"),
sidebarLayout(
sidebarPanel(),
mainPanel(
h2("About"),
p("This is a Shiny app that provides insights into the salaries of employees of post-secondary schools in BC."),
p("The vancouver Sun has collected the names and salaries of workers that earn a yearly salary of more than $75,000 from
post-secondary schools in British Columbia."),
p("Please navigate to the tabs above to explore the data."),
img(src = "VancouverSun.png", width = "300px", height = "300px")
)
)
)
# Define UI for the main app
ui_main <- fluidPage(
titlePanel("Employee Salaries Data"),
tabsetPanel(
tabPanel("Table",
sidebarLayout(
sidebarPanel(
checkboxInput("sort", "Sort table by selected variable"),
selectInput("filter", "Filter by Sector:",
choices = c("All", unique(data$Sector))),
downloadButton("downloadCSV", "Download CSV")
),
mainPanel(
DT::dataTableOutput("table"),
textOutput("result_count")
)
)
),
tabPanel("Plots",
sidebarLayout(
sidebarPanel(
h4("Select Plot"),
radioButtons("plot_type", "Choose a plot:",
choices = c("Average Salary", "Total Salary", "Total Employees")),
# Adding a slider input for salary threshold
sliderInput("salary_threshold", "Select Salary Threshold:",
min = 0, max = max(data$Remuneration, na.rm = TRUE), value = 75000)
),
mainPanel(
plotOutput("selected_plot")
)
)
)
)
)
# Define server logic
server <- function(input, output) {
agency_summary <- function(threshold) {
# Filter data based on the threshold
filtered_data <- subset(data, Remuneration > threshold)
# Calculate total salary
total_salary <- aggregate(Remuneration ~ Agency, data = filtered_data, FUN = sum, na.rm = TRUE)
# Calculate total employees
total_employees <- aggregate(Remuneration ~ Agency, data = filtered_data, FUN = length)
# Merge total salary and total employees by Agency
summarized_data <- merge(total_salary, total_employees, by = "Agency")
# Calculate average salary
summarized_data$AverageSalary <- summarized_data$Remuneration.x / summarized_data$Remuneration.y
# Rename columns
colnames(summarized_data) <- c("Agency", "TotalSalary", "TotalEmployees", "AverageSalary")
return(summarized_data)
}
filtered_data <- reactive({
filtered <- data
# Sort table if checkbox is TRUE
if(input$sort) {
filtered <- filtered[order(filtered[, input$filter]), ]
}
# Filter by selected Sector
if(input$filter != "All") {
filtered <- filtered[filtered$Sector == input$filter, ]
}
return(filtered)
})
output$table <- DT::renderDataTable({
DT::datatable(filtered_data(),
options = list(pageLength = 25))
})
output$result_count <- renderText({
paste(nrow(filtered_data()), " results found")
})
output$downloadCSV <- downloadHandler(
filename = function() {
paste("filtered_data", ".csv", sep = "")
},
content = function(file) {
filtered <- filtered_data()
write.csv(filtered, file, row.names = FALSE)
}
)
output$selected_plot <- renderPlot({
req(input$plot_type)
threshold <- input$salary_threshold
if (input$plot_type == "Average Salary") {
ggplot(data = agency_summary(threshold), aes(x = Agency, y = AverageSalary)) +
geom_bar(stat = "identity") +
labs(x = "Post-Secondary School", y = "Average Salary",
title = "Average Salary by School",
subtitle = paste("From employees that make above", scales::dollar(threshold))) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(labels = scales::dollar_format(prefix = "$"))
} else if (input$plot_type == "Total Salary") {
ggplot(data = agency_summary(threshold), aes(x = Agency, y = TotalSalary)) +
geom_bar(stat = "identity") +
labs(x = "Post-Secondary School", y = "Total Salary",
title = "Total Salary by School",
subtitle = paste("From employees that make above", scales::dollar(threshold))) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(labels = scales::dollar_format(prefix = "$"))
} else if (input$plot_type == "Total Employees") {
ggplot(data = agency_summary(threshold), aes(x = Agency, y = TotalEmployees)) +
geom_bar(stat = "identity") +
labs(x = "Post-Secondary School", y = "Total Employees",
title = "Number of employees that make above certain salary") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
}
})
}
# Run the application
shinyApp(
ui = navbarPage(
"Employee Salaries App",
tabPanel("Home", ui_home),
tabPanel("Data Analysis", ui_main)
),
server = server
)
But now I want to have multiple pages. Each page would be a separate dataset. I saw in another post that using modules would be the best way to go about this. However, when I try that, I see the filter panels looks fine for the table and plot but the table and plots don't show up (which I assume means that it's something to do with the server command ?). My ui.R, server.R and module code is below.
ui.R
# Load necessary libraries
library(shiny)
library(shinydashboard)
library(DT)
library(ggplot2)
library(scales)
dashboardPage(
dashboardHeader(
title = "Public Sector Salaries Database"
),
dashboardSidebar(
sidebarMenu(id = "tabs",
menuItem("Post-Secondary Schools", tabName = "module_uni"),
menuItem("Healthcare Agencies", tabName = "module_health"),
menuItem("Public School Districts", tabName = "module_schools")
),
tags$hr(),
uiOutput("globalenv"),
div(style="padding: 20px;",
helpText("app by ", a("Irvin Ng", href="www.linkedin.com/in/irvin-ng-605424120", target="_blank"))
)
),
dashboardBody(
uiOutput("moduleUI")
)
)
server.R
library(shiny)
library(shinydashboard)
library(DT)
library(ggplot2)
library(scales)
data <- read.csv("universities.csv", stringsAsFactors = FALSE)
function(input, output, session) {
vals <- reactiveValues(
selected_tab_new = NULL,
selected_tab_old = NULL
)
observeEvent(input$tabs, {
source(paste0("modules/", input$tabs, ".R"), local = FALSE)
vals$selected_tab_new <- input$tabs
if(!is.null(vals$selected_tab_old)) {
rm(list = c(vals$selected_tab_old, paste0(vals$selected_tab_old, "UI")), pos = globalenv())
}
vals$selected_tab_old <- input$tabs
})
observe({
if (!is.null(vals$selected_tab_new)) {
output$moduleUI <- renderUI({
rlang::as_function(paste0(vals$selected_tab_new, "UI"))(vals$selected_tab_new)
})
callModule(rlang::as_function(vals$selected_tab_new), id = vals$selected_tab_new)
}
})
}
module_uni.R
# Load necessary libraries
library(shiny)
library(DT)
library(ggplot2)
library(scales)
# Load the data
data <- read.csv("universities.csv", stringsAsFactors = FALSE)
# Define UI for the main app
module_uniUI <- function(id) {
ns <- NS(id)
fluidPage(
titlePanel("Post-Secondary School Salaries"),
tabsetPanel(
tabPanel("Table",
sidebarLayout(
sidebarPanel(
checkboxInput("sort", "Sort table by selected variable"),
selectInput("filter", "Filter by Agency:",
choices = c("All", unique(data$Agency))),
downloadButton("downloadCSV", "Download CSV")
),
mainPanel(
DT::dataTableOutput("table"),
textOutput("result_count")
)
)
),
tabPanel("Plots",
sidebarLayout(
sidebarPanel(
h4("Select Plot"),
radioButtons("plot_type", "Choose a plot:",
choices = c("Average Salary", "Total Salary", "Total Employees")),
# Adding a slider input for salary threshold
sliderInput("salary_threshold", "Select Salary Threshold:",
min = 0, max = max(data$Remuneration, na.rm = TRUE), value = 75000)
),
mainPanel(
plotOutput("selected_plot")
)
)
)
)
)
}
module_uni <- function(input, output, data) {
agency_summary <- function(threshold) {
# Filter data based on the threshold
filtered_data <- subset(data, Remuneration > threshold)
# Calculate total salary
total_salary <- aggregate(Remuneration ~ Agency, data = filtered_data, FUN = sum, na.rm = TRUE)
# Calculate total employees
total_employees <- aggregate(Remuneration ~ Agency, data = filtered_data, FUN = length)
# Merge total salary and total employees by Agency
summarized_data <- merge(total_salary, total_employees, by = "Agency")
# Calculate average salary
summarized_data$AverageSalary <- summarized_data$Remuneration.x / summarized_data$Remuneration.y
# Rename columns
colnames(summarized_data) <- c("Agency", "TotalSalary", "TotalEmployees", "AverageSalary")
return(summarized_data)
}
filtered_data <- reactive({
filtered <- data
# Sort table if checkbox is TRUE
if(input$sort) {
filtered <- filtered[order(filtered[, input$filter]), ]
}
# Filter by selected Sector
if(input$filter != "All") {
filtered <- filtered[filtered$Sector == input$filter, ]
}
return(filtered)
})
output$table <- DT::renderDataTable({
DT::datatable(filtered_data(),
options = list(pageLength = 25))
})
output$result_count <- renderText({
paste(nrow(filtered_data()), " results found")
})
output$downloadCSV <- downloadHandler(
filename = function() {
paste("filtered_data", ".csv", sep = "")
},
content = function(file) {
filtered <- filtered_data()
write.csv(filtered, file, row.names = FALSE)
}
)
output$selected_plot <- renderPlot({
req(input$plot_type)
threshold <- input$salary_threshold
if (input$plot_type == "Average Salary") {
ggplot(data = agency_summary(threshold), aes(x = Agency, y = AverageSalary)) +
geom_bar(stat = "identity") +
labs(x = "School", y = "Average Salary",
title = "Average Salary by Post-Secondary School",
subtitle = paste("From employees that make above", scales::dollar(threshold))) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(labels = scales::dollar_format(prefix = "$"))
} else if (input$plot_type == "Total Salary") {
ggplot(data = agency_summary(threshold), aes(x = Agency, y = TotalSalary)) +
geom_bar(stat = "identity") +
labs(x = "School", y = "Total Salary",
title = "Total Salary by Post-Secondary School",
subtitle = paste("From employees that make above", scales::dollar(threshold))) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(labels = scales::dollar_format(prefix = "$"))
} else if (input$plot_type == "Total Employees") {
ggplot(data = agency_summary(threshold), aes(x = Agency, y = TotalEmployees)) +
geom_bar(stat = "identity") +
labs(x = "School", y = "Total Employees",
title = paste("Number of employees that make above", scales::dollar(threshold))) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
}
})
}