Related
I am developing a Shiny app to process temperature data. Sometimes, temperatures loggers are set up in the lab and start measuring before actually being deployed in the field. Therefore, I need to allow the user to crop the data to the actual on-site measurements.
The upload and cropping are both triggered by actionButtons because they require other inputs (time format, delimiter etc.) I have not included in the MWE.
To avoid mixing up datasets, I would like the previous cropped data to be hidden (or better, set to NULL) if a new raw dataset is uploaded.
I tried the following:
ui
library("tidyverse")
library("magrittr")
library("DT")
library("xts")
library("shiny")
library("shinydashboard")
ui <- dashboardPage(
skin = "green",
dashboardHeader(title = "MWE"),
dashboardSidebar(
sidebarMenu(
menuItem("Upload data", tabName = "upload")
)
),
dashboardBody(
tabItems(
tabItem(tabName = "upload",
fluidRow(
box(
title = "Upload settings",
width = 5,
fileInput("fname", "Data", buttonLabel = "Browse..."),
actionButton("uploadbtn", "Upload")
),
box(
title = "Raw data",
width = 7,
DTOutput("raw_table")
)
),
fluidRow(
uiOutput("crop_box"),
box(
width = 8,
h4(strong("Do you wish to crop your data to the selected times?")),
br(),
actionButton("cropbtn", "Crop")
)
),
fluidRow(
box(
title = "Cropped dataset",
width = 12,
DTOutput("cropped_table")
)
)
)
)
)
)
server
server <- function(input, output, session) {
uploaded <- reactiveVal(FALSE)
observeEvent(input$uploadbtn, {
uploaded(TRUE)
})
# Upload raw data
raw <- bindEvent(reactive({
req(input$fname)
# Read in data as zoo object the convert to xts
read.delim.zoo(file = input$fname$datapath,
sep = "\t",
skip = 0,
header = TRUE,
dec = ".",
drop = FALSE,
FUN = as.POSIXct,
tz = "",
format = "%Y.%m.%d %H:%M:%S") %>% as.xts
}),
input$uploadbtn # Only upload once button is clicked
)
# Display raw data
output$raw_table <- renderDT({
req(raw())
datatable(as.data.frame(raw()), options = list(scrollX = TRUE))
})
# Select beginning and end of dataset
first_data <- reactive({
raw() %>% first("1 days") %>% zoo
})
last_data <- reactive({
raw() %>% last("1 days") %>% zoo
})
output$crop_box <- renderUI({
box(
width = 4,
h4(strong("Select the start and end time of on-site measurements.")),
sliderInput("onsite_start", "Start of on-site measurements",
min = as.POSIXct(min(index(first_data()))),
max = as.POSIXct(max(index(first_data()))),
value = min(index(first_data())),
timeFormat = "%F %R"),
sliderInput("onsite_end", "End of on-site measurements",
min = as.POSIXct(min(index(last_data()))),
max = as.POSIXct(max(index(last_data()))),
value = max(index(last_data())))
)
})
cropped <- bindEvent(reactive({
req(raw(), uploaded())
start_indx <- index(raw()) >= as.POSIXct(input$onsite_start) # Get start
end_indx <- index(raw()) <= as.POSIXct(input$onsite_end) # Get end
raw()[which(start_indx & end_indx), , drop = FALSE]
}),
input$cropbtn # Only compute once button is clicked
)
output$cropped_table <- renderDT({
req(cropped())
cropped_data <- isolate(cropped())
uploaded(FALSE)
datatable(as.data.frame(cropped_data))
})
observeEvent(input$uploadbtn, {
updateSliderInput(session = session, "first", value = 1)
updateSliderInput(session = session, "last", value = 1)
updateSliderInput(session = session, "onsite_start", value = as.POSIXct(min(index(first_data()))))
updateSliderInput(session = session, "onsite_end", value = as.POSIXct(max(index(last_data()))))
})
}
shinyApp(ui, server)
My plan was to use uploaded <- reactiveVal(TRUE) as a flag, and to set it to FALSE once the dataset had been cropped, so that it could be set to TRUE again with a new upload. Obviously this doesn't work as the cropped dataset still shows after a new upload.
Note, however, that the updateSliderInputs work as expected when the upload button is clicked, so I gather the error must be in my strategy rather than purely my syntax.
I also tried
observeEvent(input$uploadbtn, {
cropped <<- reactive(NULL)
})
but this obviously just results in nothing being displayed at all, even after clicking input$cropbtn. I struggle to see how to build a condition that fits my needs.
I have looked at Resetting data in R shiny app when file upload fields change, Shiny resetting and updating reactiveValues dataframe with two different buttons, r - How to reset reactiveValues, and shiny - How to invalidate ractive observer using code?. Unfortunately, they did not allow me to find a solution.
Please find sample data here and here (the same data with different dates so you can tell them apart easily).
Try with a reactiveValues object that is set to NULL when a new data file is uploaded.
server <- function(input, output, session) {
croppedd <- reactiveValues(data=NULL)
# uploaded <- reactiveVal(FALSE)
# observeEvent(input$uploadbtn, {
# uploaded(TRUE)
# })
# Upload raw data
raw <- bindEvent(reactive({
req(input$fname)
# Read in data as zoo object the convert to xts
read.delim.zoo(file = input$fname$datapath,
sep = "\t",
skip = 0,
header = TRUE,
dec = ".",
drop = FALSE,
FUN = as.POSIXct,
tz = "",
format = "%Y.%m.%d %H:%M:%S") %>% as.xts
}),
input$uploadbtn # Only upload once button is clicked
)
# Display raw data
output$raw_table <- renderDT({
req(raw())
datatable(as.data.frame(raw()), options = list(scrollX = TRUE))
})
# Select beginning and end of dataset
first_data <- reactive({
raw() %>% first("1 days") %>% zoo
})
last_data <- reactive({
raw() %>% last("1 days") %>% zoo
})
output$crop_box <- renderUI({
box(
width = 4,
h4(strong("Select the start and end time of on-site measurements.")),
sliderInput("onsite_start", "Start of on-site measurements",
min = as.POSIXct(min(index(first_data()))),
max = as.POSIXct(max(index(first_data()))),
value = min(index(first_data())),
timeFormat = "%F %R"),
sliderInput("onsite_end", "End of on-site measurements",
min = as.POSIXct(min(index(last_data()))),
max = as.POSIXct(max(index(last_data()))),
value = max(index(last_data())))
)
})
cropped <- bindEvent(reactive({
req(raw(), uploaded())
start_indx <- index(raw()) >= as.POSIXct(input$onsite_start) # Get start
end_indx <- index(raw()) <= as.POSIXct(input$onsite_end) # Get end
raw()[which(start_indx & end_indx), , drop = FALSE]
}),
input$cropbtn # Only compute once button is clicked
)
observe({
croppedd$data <- cropped()
})
output$cropped_table <- renderDT({
req(cropped())
# cropped_data <- isolate(cropped())
# uploaded(FALSE)
datatable(as.data.frame(croppedd$data))
})
observeEvent(input$uploadbtn, {
croppedd$data <- NULL
# updateSliderInput(session = session, "first", value = 1)
# updateSliderInput(session = session, "last", value = 1)
updateSliderInput(session = session, "onsite_start", value = as.POSIXct(min(index(first_data()))))
updateSliderInput(session = session, "onsite_end", value = as.POSIXct(max(index(last_data()))))
})
}
I would like to select one sample from my dataset in a dropdown menu.
As there are many samples to choose from, I would like to narrow down the selectable samples by selecting the values of additional data columns in checkbox dropdown menus.
I can successfully create the checkbox dropdown menus and and print the selections made there.
I am struggling with feeding these same selected values into the dropdown menu that chooses the sample. I want to make the choices of the selectInput reactive but can't access the variables the same way as inside renderText().
See comments in the code for where I am stuck.
Thanks!
library(shiny)
library(dplyr)
##################################
#### checkbox dropdown module ####
##################################
mod_ui_checkbox_dropdown_ui <- function(id){
ns <- NS(id)
tagList(
uiOutput(ns("choice_dropdown"))
)
}
mod_ui_checkbox_dropdown_server <- function(id, dropdown_label = "Items", menu_choices = c("item1", "item2", "item3"), dropdown_status = "default"){
dropdownButton <- function(label = "", status = c("default", "primary", "success", "info", "warning", "danger"), ..., width = NULL) {
status <- match.arg(status)
# dropdown button content
html_ul <- list(
class = "dropdown-menu",
style = "max-width: 300px;",
#style = if (!is.null(width))
# paste0("width: ", validateCssUnit(width), ";"),
lapply(X = list(...), FUN = tags$li, style = "margin-left: 10px; margin-right: 10px;")
)
# dropdown button appearance
html_button <- list(
class = paste0("btn btn-", status," dropdown-toggle"),
style = "width: 100%; max-width: 300px; display: flex; justify-content: space-between;",
type = "button",
`data-toggle` = "dropdown"
)
html_button <- c(html_button, list(label))
html_button <- c(html_button, list(tags$span(class = "caret", style = "margin-top: 8px;")))
# final result
tags$div(
class = "dropdown",
do.call(tags$button, html_button),
do.call(tags$ul, html_ul),
tags$script(
"$('.dropdown-menu').click(function(e) {
e.stopPropagation();
});")
)
}
moduleServer( id, function(input, output, session){
ns <- session$ns
# output$selected_items <- renderPrint({
# input$dropdown_checkboxes
# })
output$choice_dropdown <- renderUI({
dropdownButton(
label = dropdown_label, status = dropdown_status, width = "100%",
actionButton(inputId = ns("all"), label = "all/none", class = "btn btn-sm", style = "margin-bottom: 8px;"),
checkboxGroupInput(inputId = ns("dropdown_checkboxes"), label = NULL, choices = menu_choices, selected = menu_choices)
)
})
# Select all / Unselect all
observeEvent(input$all, {
if (is.null(input$dropdown_checkboxes)) {
updateCheckboxGroupInput(
session = session, inputId = "dropdown_checkboxes", selected = menu_choices
)
} else {
updateCheckboxGroupInput(
session = session, inputId = "dropdown_checkboxes", selected = ""
)
}
})
# return selected values
return(reactive(input$dropdown_checkboxes))
})
}
##################
#### main app ####
##################
ui <- fluidPage(
textOutput("text"),
uiOutput("sample_dropdown"),
#uiOutput("manual_checks"),
br(),
br(),
uiOutput("sample_filters"),
)
server <- function(input, output, session) {
data <- tibble::tibble("Sample ID" = c("Sample1", "Sample2", "Sample3", "Sample4", "Sample5", "Sample6"),
"Group" = c("group1", "group1", "group2", "group2", "group3", "group3"),
"Name" = c("Aime", "Balthasar", "Charlotte", "Daniel", "Emilie", "Fiona"))
ns <- session$ns
# helper function. filters dataframe columns for only the entries listed in selected_values
# selected_values is a list with an entry for each filter dropdown that holds a reactive vector with the selected items from each dropdown
filter_selection <- function(data, selected_values){
# loop through list by names of the list items
for (val in names(selected_values)){
if(!is.null(val)){
data <- dplyr::filter(data, .data[[val]] %in% selected_values[[val]]())
}
}
return(data)
}
### select sample ###
# get selectable samples from study data
selectable_samples <- reactive({
##### HERE BE DRAGONS
##### THIS DOES NOT WORK: When I try to access the values from the checkboxes here in this reactive element, I only get the empty list().
##### How do I make this reactive element respond to the selected values?
### ONLY PRINTS EMPTY LIST ###
print(selected_values)
### THIS GIVES AN ERROR ###
#print(selected_values$Name())
data %>%
# filter the choices based on the selected values here
{if(length(selected_values) != 0) filter_selection(., selected_values) else .} %>%
dplyr::select(any_of("Sample ID")) %>%
unique() %>%
pull() %>%
sort()
})
output$sample_dropdown <- renderUI({
selectInput("sample_dropdown", label = NULL, choices = selectable_samples())
})
## ---- sample filters ##
## this dynamically creates checkbox dropdown menus for selected filter columns
sample_filter_cols <- c("Group", "Name")
# create filter module UI elements
output$sample_filters <- renderUI(
sapply(sample_filter_cols, function(fav){
mod_ui_checkbox_dropdown_ui(stringr::str_replace_all(fav, " ", "-")) #IDs don't like spaces
})
)
# capture filter module outputs in list
# solution using a list and observe() adapted from here
# https://stackoverflow.com/questions/57802428/looping-shiny-callmodule-only-exports-last-value
selected_values <- list()
observe(
selected_values <<- sapply(sample_filter_cols, function(x){
choices <- data %>%
select(any_of(x)) %>%
unique() %>%
pull() %>%
sort()
mod_ui_checkbox_dropdown_server(stringr::str_replace_all(x, " ", "-"), dropdown_label = x, menu_choices = choices)
}, USE.NAMES = TRUE)
)
##### THIS WORKS: I can access the selected values of the filter columns here and print them as text.
##### So why won't this propagate to the reactive element above?
output$text <- renderText(paste("selected values:",
paste(selected_values$Name(), collapse = " "),
paste(selected_values$Group(), collapse = " ")
))
}
shinyApp(ui, server)
Try this code. I built it without modules and it is a little bit simple, but it works.
I changed the 2nd SAMPLE ID -> to Sample 3 because I need to prove one sample with more than one group.
I hope this can help you.
library(shiny)
library(shinyWidgets)
require(tibble)
data_read <<-
tibble::tibble(
"Sample ID" = c("Sample1", "Sample3", "Sample3", "Sample4", "Sample5", "Sample6"),
"Group" = c("group1", "group1", "group2", "group2", "group3", "group3"),
"Name" = c("Aime", "Balthasar", "Charlotte", "Daniel", "Emilie", "Fiona"))
if (interactive()) {
reactive_data <- reactiveValues(
data_all = data_read,
data_sample_filter = data_read[0,],
vector_groups = NULL,
group_selected = c(),
vector_names = NULL,
)
ui <- fluidPage(
selectInput(
"sample_dropdown",
label = NULL,
choices = data_read$`Sample ID` %>% unique
),
uiOutput("group_dropdown"),
uiOutput("names_dropdown"),
verbatimTextOutput("texto"),
tableOutput("table")
)
server <- function(input, output) {
observe({
data_filter_group <<-
data_read %>%
filter(
`Sample ID` %in% input$sample_dropdown
)
output$group_dropdown <- renderUI({
pickerInput(
inputId = "group_dropdown",
label = "select group(s)",
choices = isolate(reactive_data$vector_groups),
options =
list(
`actions-box` = TRUE
),
multiple = TRUE
)
})
# Change reactive values
reactive_data$data_sample_filter <- data_filter_group
reactive_data$vector_groups <- data_filter_group$Group %>% unique
},
label = "group_dropdown UI"
)
observe({
# save reactive values input group
reactive_data$group_selected <- input[["group_dropdown"]]
data_filter_names <<-
isolate(reactive_data$data_sample_filter) %>%
filter(
Group %in% isolate(reactive_data$group_selected)
)
# Change reactive values
reactive_data$vector_names <- data_filter_names$Name %>% unique
output$names_dropdown <- renderUI({
pickerInput(
inputId = "names_dropdown",
label = "select group(s)",
choices = isolate(reactive_data$vector_names),
options = list(
`actions-box` = TRUE),
multiple = TRUE
)
})
},
label = "names_dropdown UI"
)
# example all data output
output$table <-
renderTable({
data_read
})
output$texto <-
renderText({
paste("selected values:\n",
input$sample_dropdown,"\n\t",
paste( input[["group_dropdown"]],sep="" ,collapse = "\n\t"),"\n\t\t",
paste( input[["names_dropdown"]],sep="" ,collapse = "\n \t\t")
)
})
}
shinyApp(ui, server)
}
SOLVED: Thanks to Yeyo's suggestion of using pickerInput I was able to do away with my monstrosity of a custom widget module and get this to work nicely with much less code!
library(shiny)
library(shinyWidgets)
library(dplyr)
#' Helper Function
#'
#' #description helper function to filter the choices of a dropdown menu based on selected data
#'
#' #return character vector with choices that remain after filtering the data
#'
#' #param data data frame to be filtered
#' #param choice_col name of the column that holds all possible values for the dropdown menu. e.g. if the dropdown menu chooses sample ids, this may be the "Sample ID" column
#' #param filter_selections list with selected values from all filter dropdown menus. e.g. list("Group" = c("group1", "group2), "Names" = c("Aime", "Balthasar")), will produce all samples that are in these two groups and belong to these two names.
#'
#' #noRd
filter_choices <- function(data, choice_col, filter_selections){
choices <- data
for (filter_col in names(filter_selections)){
choices <- choices %>%
dplyr::filter(., .data[[filter_col]] %in% filter_selections[[filter_col]])
}
choices <- choices %>%
dplyr::select(any_of(choice_col)) %>%
unique() %>%
pull() %>%
sort()
return(choices)
}
##################
#### main app ####
##################
ui <- fluidPage(
uiOutput("sample_dropdown"),
h3("Filters"),
uiOutput("sample_filters")
)
server <- function(input, output, session) {
data <- tibble::tibble("Sample ID" = c("Sample1", "Sample2", "Sample3", "Sample4", "Sample5", "Sample6"),
"Group" = c("group1", "group1", "group2", "group2", "group3", "group3"),
"Name" = c("Aime", "Balthasar", "Charlotte", "Daniel", "Emilie", "Fiona"))
### dropdown to select a sample from the data ###
selectable_samples <- reactive({
filter_choices(data, choice_col = "Sample ID", filter_selections = selected_values())
})
output$sample_dropdown <- renderUI({
shinyWidgets::pickerInput("sample_dropdown", label = "Select Sample", choices = selectable_samples())
})
### ---- dropdowns to narrow down choices of samples I am interested in ###
# specify which data columns you want to be able to filter by
sample_filter_cols <- c("Group", "Name")
# create picker UI elements for these columns
output$sample_filters <- renderUI(
div(
lapply(sample_filter_cols, function(x){
choices <- data %>%
select(any_of(x)) %>%
unique() %>%
pull() %>%
sort()
shinyWidgets::pickerInput(stringr::str_replace_all(x, " ", "-"),
label = x,
choices = choices,
multiple = TRUE,
selected = choices,
options = list(`actions-box` = TRUE))
})
)
)
# collect output of filter dropdown menus in a reactive list
selected_values <- reactive({
sapply(sample_filter_cols, function(x){
input[[stringr::str_replace_all(x, " ", "-")]]
}, USE.NAMES = TRUE)
})
}
shinyApp(ui, server)
I tried to keep reprex as simple as possible.
I want to save with the ADD button currently chosen inputs, inside Data Frame (selected by index passed by userId input), which is inside the list, and later on use this Data Frame to render a table (in the final app make a plot).
Here I figured out, how to save values inside the data frame. (not data frame inside a list)
How to save input to data frame, and use it later in Shiny?
Now Add button returns this:
Warning: Error in choosen_user: unused argument (rbind(choosen_user(), new_day_rate())) <- this is propably because I used reactive() not reactiveVal(), but with reactiveVal() there is this error:
Warning: Error in .getReactiveEnvironment()$currentContext: Operation not allowed without an active reactive context.
You tried to do something that can only be done from inside a reactive consumer.
library(shiny)
# Saved_users_list normally came from external file
saved_users_list <- list(data.frame(date = c(as.Date("2022-04-18"),
as.Date("2022-04-19")),
rate = c(8,1),
day_comment = c("Found a gf",
"Broke my arm")),
data.frame(date = c(as.Date("2022-04-18"),
as.Date("2022-04-19")),
rate = c(10,1),
day_comment = c("Found a job",
"They fired me")))
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("userId", "userId", choices = c(1:2)),
sliderInput("day_rate", "Rate your day", min = 0, max = 10, value = 5, step = 0.5),
dateInput("date", "Pick a date"),
textAreaInput("comment", "Comment", placeholder = "Add a description (OPTIONAL)"),
actionButton("add", "Add"),
actionButton("test", "Test values") # Button to test inputs values
),
mainPanel(
tableOutput("test_table")
)
)
)
server <- function(input, output, session) {
users_list <- reactiveVal(saved_users_list)
selected_user <- reactive(as.numeric(input$userId))
output$test_table <- renderTable({
users_list()[selected_user()]
})
new_day_rate <- reactive(list(data.frame(date = input$date,
rate = input$day_rate,
day_comment = input$comment)))
choosen_user <- reactive(users_list()[[selected_user()]])
# Button to add values to the data frame inside users_list
observeEvent(input$add, {
# users_list()[[selected_user()]] <- rbind(users_list()[[selected_user()]], as.data.frame(new_day_rate())) # Error in <-: invalid (NULL) left side of assignment
choosen_user(rbind(choosen_user(), new_day_rate())) # Here I tried to implement a solution from linked question
})
# Button to test inputs values
observeEvent(input$test, {
message("userId: ", input$userId, " ", class(input$userId))
message("selected_user(): ", selected_user())
message("new_day_rate(): ", new_day_rate())
message("str(new_day_rate()): ", str(new_day_rate()))
message("users_list()[[selected_user()]]: ",users_list()[[selected_user()]])
})
}
shinyApp(ui, server)
I think you're after reactiveValues? Something like:
library(shiny)
# Saved_users_list normally came from external file
saved_users_list <- list(
data.frame(
date = c(as.Date("2022-04-18"), as.Date("2022-04-19")),
rate = c(8,1),
day_comment = c("Found a gf", "Broke my arm")
),
data.frame(
date = c(as.Date("2022-04-18"), as.Date("2022-04-19")),
rate = c(10,1),
day_comment = c("Found a job", "They fired me")
)
)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("userId", "userId", choices = c(1:2)),
sliderInput("day_rate", "Rate your day", min = 0, max = 10, value = 5, step = 0.5),
dateInput("date", "Pick a date"),
textAreaInput("comment", "Comment", placeholder = "Add a description (OPTIONAL)"),
actionButton("add", "Add"),
actionButton("test", "Test values") # Button to test inputs values
),
mainPanel(
tableOutput("test_table")
)
)
)
server <- function(input, output, session) {
cache <- reactiveValues(saved_users = saved_users_list)
selected_user <- reactive(as.numeric(input$userId))
output$test_table <- renderTable({
cache$saved_users[selected_user()]
})
new_day_rate <- reactive(
data.frame(
date = as.Date(input$date),
rate = input$day_rate,
day_comment = input$comment
)
)
observeEvent(input$add, {
cache$saved_users[[selected_user()]] <- rbind(
cache$saved_users[[selected_user()]], new_day_rate()
)
})
observeEvent(input$test, {
message("userId: ", input$userId, " ", class(input$userId))
message("selected_user(): ", selected_user())
message("new_day_rate(): ", new_day_rate())
message("str(new_day_rate()): ", str(new_day_rate()))
message("users_list()[[selected_user()]]: ", cache$saved_users[[selected_user()]])
})
}
shinyApp(ui, server)
I am building a shiny budgeting shiny application that prompts the user to enter data such as what type of expense was spent, the amount, and a description. I would like to display a line plot in the second pannel of the application labeled "Monthly Budget" ONLY when the user has entered at least one data entry where the category is "Savings". I have tried experimenting with things such as hiding/displaying the plot whenever the condition is met, but it seems that I always get a NaN error message with this approach. Thus, I am experimenting with conditionalPanel() in hopes of accomplishing this task. I've noticed similar posts to this one, however this is the first case that I have found where conditionalPanel() deals with data that the user inputs as opposed to a given dataset. In the code below I get the following error message: "Error in: Invalid input: date_trans works with objects of class Date only".
Here is the code:
# Libraries
library(shiny)
library(ggplot2)
library(shinycssloaders)
library(colortools)
library(shinythemes)
library(DT)
library(tidyverse)
library(kableExtra)
library(formattable)
library(xts)
# Creating Contrasting Colors For Buckets
bucket_colors <- wheel("skyblue", num = 6)
# Define UI for application that draws a histogram
ui <- fluidPage(
# theme = shinytheme("spacelab"),
shinythemes::themeSelector(),
## Application Title
titlePanel("2021 Budgeting & Finances"),
tags$em("By:"),
tags$hr(),
navbarPage("", id = "Budget",
tabPanel("Data Entry",
div(class = "outer",
# Sidebar Layout
sidebarLayout(
sidebarPanel(
selectInput("Name",
label = "Name:",
choices = c("","Jack", "Jill")),
selectInput("Bucket",
label = "Item Bucket:",
choices = c("","Essential", "Non-Essential", "Savings", "Rent/Bills", "Trip", "Other")),
textInput("Item",
label = "Item Name:",
placeholder = "Ex: McDonald's"),
shinyWidgets::numericInputIcon("Amount",
"Amount:",
value = 0,
step = 0.01,
min = 0,
max = 1000000,
icon = list(icon("dollar"), NULL)),
dateInput("Date",
label = "Date",
value = Sys.Date(),
min = "2021-05-01",
max = "2022-12-31",
format = "M-d-yyyy"),
actionButton("Submit", "Submit", class = "btn btn-primary"),
downloadButton("Download", "Download")),
# Show a plot of the generated distribution
mainPanel(
tableOutput("PreviewTable")
)
)
)
),
############ THIS IS WHERE THE ERROR HAPPENS #############
tabPanel("Monthly Budget",
conditionalPanel("output.any(ReactiveDf() == 'Savings') == TRUE ",
plotOutput("SavingsPlot")
)
########### THIS IS WHERE THE ERROR HAPPENS ##############
),
tabPanel("Budget to Date",
tableOutput("YearTable")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output, session) {
## SAVE DATA
# Set Up Empty DF
df <- tibble("Name" = character(),
"Date" = character(),
"Category" = character(),
"Amount" = numeric(),
"Description" = character())
# DF is made reactive so we can add new lines
ReactiveDf <- reactiveVal(value = df)
# Add inputs as new data (lines)
observeEvent(input$Submit, {
if (input$Bucket == "" | input$Amount == 0 |
is.na(input$Amount)) {
return(NULL)
}
else {
# New lines are packaged together in a DF
new_lines <- data.frame(Name = as.character(input$Name),
Date = as.character(input$Date),
Category = input$Bucket,
Amount = as.character(input$Amount),
Description = as.character(input$Item))
# change df globally
df <<- rbind(df, new_lines)
# ensure amount is numeric
df <<- df %>%
mutate("Amount" = as.numeric(Amount))
# Update reactive values
ReactiveDf(df)
#clear out original inputs now that they are written to df
updateSelectInput(session, inputId = "Name", selected = "")
updateSelectInput(session, inputId = "Bucket", selected = "")
updateNumericInput(session, inputId = "Amount", value = 0)
updateTextInput(session, inputId = "Item", value = "")
}
})
## Preview Table
observeEvent(input$Submit, {
output$PreviewTable <-
function(){
ReactiveDf()[order(ReactiveDf()$Date, decreasing = TRUE),] %>%
kable("html") %>%
kable_material(c("striped", "hover")) %>%
kable_styling("striped", full_width = TRUE) %>%
column_spec(3, color = "black", background = ifelse(ReactiveDf()[3]=="Essential", "#87CEEB", ifelse(ReactiveDf()[3] == "Non-Essential", "#EBA487", ifelse(ReactiveDf()[3] == "Savings", "#87EBA4", ifelse(ReactiveDf()[3] == "Rent/Bills", "#A487EB", ifelse(ReactiveDf()[3] == "Trip", "#CEEB87", "#EB87CE")))))) %>%
column_spec(1, color = ifelse(ReactiveDf()[1] == "Ashley", "lightpink", "lightcyan"))
}
########## THIS IS THE LINE PLOT I AM TRYING TO RENDER ##########
output$SavingsPlot <- renderPlot({
savings <- ReactiveDf()[ReactiveDf()$Category == "Savings",]
savings <- savings[, -c(1,3,5)]
savings$Date <- as.Date(savings$Date)
savings$Amount <- as.numeric(savings$Amount)
savings <- as.xts(savings$Amount, order.by = as.Date(savings$Date))
weekly <- apply.weekly(savings,sum)
weekly_savings <- as.data.frame(weekly)
weekly_savings$names <- rownames(weekly_savings)
rownames(weekly_savings) <- NULL
colnames(weekly_savings) <- c("Amount", "Date")
Expected <- NULL
for(i in 1:dim(weekly_savings)[1]){
Expected[i] <- i * 625
}
weekly_savings$Expected <- Expected
ggplot(weekly_savings, aes(x = Date)) +
geom_line(aes(y = Expected), color = "red") +
geom_line(aes(y = Amount), color = "blue") +
ggtitle("House Downpayment Savings Over Time") +
ylab("Dollars") +
scale_x_date(date_minor_breaks = "2 day") +
scale_y_continuous(labels=scales::dollar_format())
})
})
########## THIS IS THE LINE PLOT I AM TRYING TO RENDER ##########
# Downloadable csv of selected dataset ----
output$Download <- downloadHandler(
filename = function() {
paste("A&J Budgeting ", Sys.Date(),".csv", sep = "")
},
content = function(file) {
write.csv(ReactiveDf(), file, row.names = FALSE)
}
)
# use if df new lines have errors
observeEvent(input$start_over, {
# change df globally
df <- tibble("Name" = character(),
"Date" = character(),
"Expense Category" = character(),
"Amount" = numeric(),
"Description" = character())
# Update reactive values to empty out df
ReactiveDf(df)
})
## MONTHLY TABLE
output$MonthlyTable <- renderTable({
ReactiveDf()
})
## YEAR TO DATE TABLE
output$YearTable <- renderTable({
ReactiveDf()
})
}
# Run the application
shinyApp(ui = ui, server = server)
We can use a condition like nrow(filter(ReactiveDf(), Category == 'Savings')) > 0 as if ReactiveDf is a normal df. Also, when converting the xts object to a df the Date column was coerced to character.
app:
# Libraries
library(shiny)
library(tidyverse)
library(shinycssloaders)
library(colortools)
library(shinythemes)
library(DT)
library(tidyverse)
library(kableExtra)
library(formattable)
library(xts)
library(lubridate)
# Creating Contrasting Colors For Buckets
bucket_colors <- wheel("skyblue", num = 6)
# Define UI for application that draws a histogram
ui <- fluidPage(
# theme = shinytheme("spacelab"),
shinythemes::themeSelector(),
## Application Title
titlePanel("2021 Budgeting & Finances"),
tags$em("By:"),
tags$hr(),
navbarPage("", id = "Budget",
tabPanel("Data Entry",
div(class = "outer",
# Sidebar Layout
sidebarLayout(
sidebarPanel(
selectInput("Name",
label = "Name:",
choices = c("","Jack", "Jill")),
selectInput("Bucket",
label = "Item Bucket:",
choices = c("","Essential", "Non-Essential", "Savings", "Rent/Bills", "Trip", "Other")),
textInput("Item",
label = "Item Name:",
placeholder = "Ex: McDonald's"),
shinyWidgets::numericInputIcon("Amount",
"Amount:",
value = 0,
step = 0.01,
min = 0,
max = 1000000,
icon = list(icon("dollar"), NULL)),
dateInput("Date",
label = "Date",
value = Sys.Date(),
min = "2021-05-01",
max = "2022-12-31",
format = "M-d-yyyy"),
actionButton("Submit", "Submit", class = "btn btn-primary"),
downloadButton("Download", "Download")),
# Show a plot of the generated distribution
mainPanel(
tableOutput("PreviewTable")
)
)
)
),
tabPanel("Monthly Budget",
plotOutput("SavingsPlot")
),
tabPanel("Budget to Date",
tableOutput("YearTable")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output, session) {
## SAVE DATA
# Set Up Empty DF
df <- tibble("Name" = character(),
"Date" = character(),
"Category" = character(),
"Amount" = numeric(),
"Description" = character())
# DF is made reactive so we can add new lines
ReactiveDf <- reactiveVal(value = df)
# Add inputs as new data (lines)
observeEvent(input$Submit, {
if (input$Bucket == "" | input$Amount == 0 |
is.na(input$Amount)) {
return(NULL)
}
else {
# New lines are packaged together in a DF
new_lines <- data.frame(Name = as.character(input$Name),
Date = as.character(input$Date),
Category = input$Bucket,
Amount = as.character(input$Amount),
Description = as.character(input$Item))
# change df globally
df <<- rbind(df, new_lines)
# ensure amount is numeric
df <<- df %>%
mutate("Amount" = as.numeric(Amount))
# Update reactive values
ReactiveDf(df)
#clear out original inputs now that they are written to df
updateSelectInput(session, inputId = "Name", selected = "")
updateSelectInput(session, inputId = "Bucket", selected = "")
updateNumericInput(session, inputId = "Amount", value = 0)
updateTextInput(session, inputId = "Item", value = "")
}
})
## Preview Table
observeEvent(input$Submit, {
output$PreviewTable <-
function(){
ReactiveDf()[order(ReactiveDf()$Date, decreasing = TRUE),] %>%
kable("html") %>%
kable_material(c("striped", "hover")) %>%
kable_styling("striped", full_width = TRUE) %>%
column_spec(3, color = "black", background = ifelse(ReactiveDf()[3]=="Essential", "#87CEEB", ifelse(ReactiveDf()[3] == "Non-Essential", "#EBA487", ifelse(ReactiveDf()[3] == "Savings", "#87EBA4", ifelse(ReactiveDf()[3] == "Rent/Bills", "#A487EB", ifelse(ReactiveDf()[3] == "Trip", "#CEEB87", "#EB87CE")))))) %>%
column_spec(1, color = ifelse(ReactiveDf()[1] == "Ashley", "lightpink", "lightcyan"))
}
########## THIS IS THE LINE PLOT I AM TRYING TO RENDER ##########
if (nrow(filter(ReactiveDf(), Category == 'Savings')) > 0) {
output$SavingsPlot <- renderPlot({
savings <- filter(ReactiveDf(), Category == 'Savings')
savings$Date <- as.Date(savings$Date, format = "%Y-%m-%d")
savings$Amount <- as.numeric(savings$Amount)
savings <- as.xts(savings$Amount, order.by = savings$Date)
weekly <- apply.weekly(savings, sum)
weekly_savings <- as.data.frame(weekly)
weekly_savings$names <- rownames(weekly_savings)
rownames(weekly_savings) <- NULL
colnames(weekly_savings) <- c("Amount", "Date")
Expected <- NULL
for(i in 1:dim(weekly_savings)[1]){
Expected[i] <- i * 625
}
weekly_savings$Expected <- Expected
ggplot(weekly_savings, aes(x = ymd(Date))) +
geom_line(aes(y = Expected), color = "red") +
geom_line(aes(y = Amount), color = "blue") +
ggtitle("House Downpayment Savings Over Time") +
ylab("Dollars") +
scale_x_date(date_minor_breaks = "2 day") +
scale_y_continuous(labels=scales::dollar_format())
}) }
})
########## THIS IS THE LINE PLOT I AM TRYING TO RENDER ##########
# Downloadable csv of selected dataset ----
output$Download <- downloadHandler(
filename = function() {
paste("A&J Budgeting ", Sys.Date(),".csv", sep = "")
},
content = function(file) {
write.csv(ReactiveDf(), file, row.names = FALSE)
}
)
# use if df new lines have errors
observeEvent(input$start_over, {
# change df globally
df <- tibble("Name" = character(),
"Date" = character(),
"Expense Category" = character(),
"Amount" = numeric(),
"Description" = character())
# Update reactive values to empty out df
ReactiveDf(df)
})
## MONTHLY TABLE
output$MonthlyTable <- renderTable({
ReactiveDf()
})
## YEAR TO DATE TABLE
output$YearTable <- renderTable({
ReactiveDf()
})
}
# Run the application
shinyApp(ui = ui, server = server)
The user interface of the Shiny app I'm working on is supposed to work in the following manner:
User finds the desired observation(s) after applying a set of filters.
User clicks "Add" action button, so selected observation(s) are added to a running list/vector/etc of observations to be analyzed.
User modifies filters to find other observations which are to be included as well.
Loop back to step 1 as many times as user desires.
I cannot seem to find a way to save this list of observations to be analyzed. In the example I attached, the "observation ID" is the name of the model of the car (mtcars is used). I also did not include any data analysis, since I do not think that's necessary. In essence, the entire dataset (mtcars) should be filtered using dplyr in a reactive environment to only include the running list of selected observations.
Here's the code:
data("mtcars")
mtcars$model <- rownames(mtcars)
ui <- fluidPage(
titlePanel("sample"),
sidebarLayout(
sidebarPanel(
uiOutput("disp"),
uiOutput("qsec"),
uiOutput("model"),
actionButton("add", "Add"),
uiOutput("selectedModel")
),
mainPanel(
plotOutput("data_analysis")
)
)
)
server <- function(input, output) {
output$disp <- renderUI({
selectInput(
"disp_sel",
"Select disp:",
unique(mtcars$disp),
selected = NULL,
multiple = T,
selectize = T
)
})
output$qsec <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
selectInput(
"qsec_sel",
"Select qsec:",
unique(temp$qsec),
selected = NULL,
multiple = T,
selectize = T
)
})
output$model <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
if (!is.null(input$qsec_sel)){temp = temp %>% filter(qsec %in% input$qsec_sel)}
selectInput(
"model_sel",
"Select model:",
unique(temp$model),
selected = NULL,
multiple = T,
selectize = T
)
})
output$selectedModel <- renderUI({
req(input$add)
selectInput(
"list_of_selections",
"Selected models:",
unique(mtcars$model),
selected = NULL, # this should change when "Add" is pressed
multiple = T,
selectize = T
)
})
r_data = eventReactive(input$add,{
mtcars %>% filter(model %in% input$list_of_selections)
})
output$data_analysis <- renderPlot({
# do something with r_data (filtered data)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I've looked into modular code, reactive lists, and other stuff I don't even remember... Any help is greatly appreciated.
Try this
data("mtcars")
mtcars$model <- rownames(mtcars)
df1 <- mtcars
ui <- fluidPage(
titlePanel("sample"),
sidebarLayout(
sidebarPanel(
uiOutput("disp"),
uiOutput("qsec"),
uiOutput("model"),
actionButton("add", "Add"),
uiOutput("selectedModel")
),
mainPanel(
DTOutput("selecteddata"),
plotOutput("data_analysis")
)
)
)
server <- function(input, output) {
output$disp <- renderUI({
selectInput(
"disp_sel",
"Select disp:",
unique(mtcars$disp),
selected = NULL,
multiple = T,
selectize = T
)
})
output$qsec <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
selectInput(
"qsec_sel",
"Select qsec:",
unique(temp$qsec),
selected = NULL,
multiple = T,
selectize = T
)
})
output$model <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
if (!is.null(input$qsec_sel)){temp = temp %>% filter(qsec %in% input$qsec_sel)}
selectInput(
"model_sel",
"Select model:",
unique(temp$model),
selected = NULL,
multiple = T,
selectize = T
)
})
selected_data <- eventReactive(input$add,{
df1 %>% filter(model %in% input$model_sel)
})
output$selecteddata <- renderDT(
selected_data(), # reactive data
class = "display nowrap compact", # style
filter = "top", # location of column filters
options = list( # options
scrollX = TRUE # allow user to scroll wide tables horizontally
)
)
output$selectedModel <- renderUI({
req(input$add)
selectInput(
"list_of_selections",
"Selected models:",
choices = unique(selected_data()$model),
selected = unique(selected_data()$model), # this should change when "Add" is pressed
multiple = T,
selectize = T
)
})
r_data = eventReactive(input$add,{
mtcars %>% filter(model %in% input$list_of_selections)
})
output$data_analysis <- renderPlot({
ggplot(data=selected_data(), aes(x=disp, y=qsec)) + geom_point()
# do something with r_data (filtered data)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Found the answer. I included
selected <- reactiveValues(s = NULL)
observeEvent(input$add,{selected$s = c(selected$s, input$model})
into the server part. Then the selected models are stored in selected$s.