Related
This is the test dataset. I have created two reactive filters from the columns category and sub.category from the dataset. In addition to reactivity, both these filter values are related where a record when selected from the category then its corresponding sub category records are displayed in sub.category filter. But the problem I am noticing is when all the filters values are cleared/empty, I am not able to click the sub.category filter and see all distinct values from it. I would appreciate if this is resolved.
category<-c('AA','AA','AA','AA','AA','BB','BB','BB','BB','BB')
sub.category<- c('A01','A01','A02','A02','A03','B01','B02','B02','B03','B03')
val1<-c(1,1,2,5,2,4,3,1,1,1)
val2<-c(2,2,2,2,2,2,2,2,2,2)
val3<-c(4,5,5,6,6,3,6,8,1,1)
val4<-c(0,0,0,0,0,0,0,0,0,0)
testdata <- as.data.frame(cbind(category,sub.category,val1,val2,val3,val4)
#testdata <- read.csv("testcsv.csv")
## Filters
cat_name <- unique(testdata$category)
cat_sub_name <- testdata %>% select(category, sub.category) %>%
distinct() %>% arrange(sub.category)
# Server function to determine the input and output parameters
server <- function(input, output, session) {
### Combined ALS Metadata repository
df_testdata <- reactive({
if (is.null(input$test_category) & is.null(input$test_subcategory))
{
testdata
}
else if (is.null(input$test_category) & !is.null(input$test_subcategory))
{
testdata %>% filter(sub.category %in% input$test_subcategory)
}
else if (!is.null(input$test_category) & is.null(input$test_subcategory))
{
testdata %>% filter(category %in% input$test_category)
}
else if (!is.null(input$test_category) & !is.null(input$test_subcategory))
{
testdata %>% filter (category %in% input$test_category & sub.category %in% input$test_subcategory)
}
})
output$tab1 <- DT::renderDataTable(DT::datatable(df_testdata(), style = "bootstrap", filter = list(position = 'top', clear = FALSE), options = list(autoWidth = TRUE, searching = TRUE)))
## Dependent reactive filter
observeEvent(input$test_category, {
subcatToShow = cat_sub_name %>%
filter(category %in% input$test_category) %>% pull(sub.category)
#Update the actual input
updateSelectInput(session, "test_subcategory", choices = subcatToShow,
selected = subcatToShow[1])
})
}
# UI section of the program to design the front-end of the web application
ui <- fluidPage(
theme = shinytheme('darkly'),
titlePanel("Analysis Dataset", windowTitle="Category Dataset"
),
sidebarLayout(
mainPanel(
width = 10,
DT::dataTableOutput('tab1')
),
sidebarPanel( width = 2,
selectInput("test_category",
choices = cat_name,
label = "Select the category name",
multiple = TRUE),
selectInput("test_subcategory",
choices = c(),
label = "Select the sub category name",
multiple = TRUE)
)
)
)
shinyApp(ui = ui, server = server)`
Perhaps you are looking for this
## Dependent reactive filter
observeEvent(input$test_category, {
if (is.null(input$test_category)) {
subcatToShow = cat_sub_name$sub.category
selected <- character(0)
}else {
subcatToShow = cat_sub_name %>%
filter(category %in% input$test_category) %>%
pull(sub.category)
selected <- subcatToShow[1]
}
#Update the actual input
updateSelectInput(session, "test_subcategory", choices = subcatToShow,
selected = selected)
},ignoreNULL = FALSE)
I am creating a dynamic shiny app that works like a look up table -- it allows users to select input values and in return gives two corresponding output values (one numeric and one character) which exist in the same table.
My code needs to be dynamic, so that when the data frame changes, the user interface changes accordingly. For example, if the data table contains 3 input variables instead of 2, there needs to be one more selectInput box in the sidebar. If one variable ends up having 3 possible values instead of 2, there needs to be another option.
Thus, my code needs to:
check the updated table,
see how many variables there are and update input options in the sidebar accordingly
update range of values each of these variables has
Update the output accordingly.
Below is a simplified code:
{
library(shiny)
library(shinydashboard)
library(shinyjs)
}
Test <- data.frame(
stringsAsFactors = FALSE,
input1 = c("precarious", "precarious", "good"),
input2 = c("precarious", "moderate", "precarious"),
NumericOutput = c(3.737670877,6.688008306,8.565495761),
CharacterOutput = c("precarious", "moderate", "good")
)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
useShinyjs(),
selectInput("a", label = colnames(Test[1]),
choices = unique(Test[[1]])),
selectInput("b", colnames(Test[2]),
choices = unique(Test[[2]]))
),
dashboardBody(fluidRow(valueBoxOutput("info_box1", width = 6)),
fluidRow(valueBoxOutput("info_box2", width = 6)))
)
server <- function(input, output) {
output$info_box1 <- renderValueBox({
valueBox(
value = paste0("Score in %: ",
Test$NumericOutput[Test$input1 == input$a & Test$input2 == input$b],
collapse = ", "),
subtitle = NULL)
})
output$info_box2 <- renderValueBox({
valueBox(value = paste0(
"Assessment: ",
Test$CharacterOutput[Test$input1 == input$a & Test$input2 == input$b],
collapse = ", "),
subtitle = NULL)
})
}
shinyApp(ui, server)
Here is the outline of code. I've adopted logic you provided - input cols are the ones on which filtering is done, ouput cols are the ones on which some aggregation is done. You requested only dynamic filtering and not the output. data is reactive because from your text it's obvious you want to change datasets. Code inside its reactivity is something you need to come up with because you didn't provide any information beside Test data.frame.
library(shiny)
library(shinydashboard)
library(shinyjs)
Test <- data.frame(
stringsAsFactors = FALSE,
input1 = c("precarious", "precarious", "good"),
input2 = c("precarious", "moderate", "precarious"),
NumericOutput = c(3.737670877,6.688008306,8.565495761),
CharacterOutput = c("precarious", "moderate", "good")
)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
uiOutput("dynamicSidebar")
),
dashboardBody(fluidRow(valueBoxOutput("info_box1", width = 6)),
fluidRow(valueBoxOutput("info_box2", width = 6)))
)
server <- function(input, output){
rv <- reactiveValues()
data <- reactive({Test})
output$dynamicSidebar <- renderUI({
req(data())
rv$input_cols <- names(data()) %>% str_subset("^input")
input_values <- data() %>%
select(rv$input_cols) %>%
map(unique)
rv$input_cols %>%
map2(input_values, ~selectInput(.x, .x, choices = .y))
})
observe({
cond <- reactiveValuesToList(input) %>%
.[rv$input_cols] %>%
imap(~str_c(.y, "=='", .x, "'")) %>%
str_c(collapse = "&")
rv$filtered_data <- data() %>%
filter(eval(parse(text = cond)))
})
output$info_box1 <- renderValueBox({
req(rv$filtered_data)
my_value <- if(nrow(rv$filtered_data) > 0){
str_c(rv$filtered_data[["NumericOutput"]],collapse = ", ")
} else {
"empty data"
}
valueBox(
subtitle = "Score in %: ",
value = my_value
)
})
output$info_box2 <- renderValueBox({
req(rv$filtered_data)
my_value <- if(nrow(rv$filtered_data) > 0){
str_c(rv$filtered_data[["CharacterOutput"]], collapse = ", ")
} else {
"empy data"
}
valueBox(
subtitle = "Assessment:",
value = my_value
)
})
}
shinyApp(ui, 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.
I have a toy version of my code below. I have one of the columns (column 7) in my shiny DT output as editable. When i edit the a cell of the column it takes me back to the first row of of the column. I checked the data table object in environment, the edited cell does get update. So, that is good. But i want to stay on the same page after editing the cell. This is because a user may have applied a few filters to reach a certain page and then when he edits a cell he would like to continue from there rather than going back to start.
I am new to R so any help would be greatly appreciated.
I am using DT 0.7
My data frame has 7 columns: Continent, State, Country, Date, Rate (Pollution), Vehicles, Remark (editable column)
A user can filter the table output by select input, range and slider input. I want to make that output editable.
Thanks in advance!
library(shiny)
library(DT)
ui <- navbarPage("Hello",
tabPanel("Tab1",
sidebarLayout(
sidebarPanel( width = 4,
selectInput("continent", "Select:",
choices = ""),
selectInput("country" , "Select:",
choices = ""),
selectInput("state" , "Select:",
choices = ""),
dateRangeInput("date", "Select:",
startview = "month",
minview = "months",
maxview = "decades",
start = as.Date('1999-01-01'),
end = as.Date(today()),
separator = "-"),
sliderInput("rate", "Select:",
min = 1, max = 5, value = c(1,5),
dragRange = TRUE)),
mainPanel(
tabsetPanel(
tabPanel("Analysis",
dataTableOutput("Table1")
)))))
#server
server <- function(input, output, session)
{
observe({
updateSelectInput(session, "continent",
choices = c("All", unique(Df$Continent)))
})
observe({
updateSelectInput(session, "country",
choices = c("All", Df %>%
filter(`Continent` == input$continent) %>%
select(Country)))
})
observe({
updateSelectInput(session, "state",
choices = c("All", Df %>%
filter(`Continent` == input$continent &
`Country` == input$country) %>%
select(State)))
})
#create reactive table
RecTable <- reactive({
Df
if(input$continent != "All") {
Df <- Df[Df$Continent == input$continent,]
}
if(input$country != "All") {
Df <- Df[Df$Country == input$country,]
}
if(input$state != "All") {
Df <- Df[Df$State == input$state,]
}
Df <- Df %>%
filter(Date >= input$date[1] & Date <= input$date[2]) %>%
filter(Rate >= input$rate[1] & Rate <= input$rate[2])
Df})
output$Table1 <- DT::renderDT({
DT::datatable(RecTable(),
rownames = FALSE ,
editable = list(target = 'cell', disable = list(columns = c(0:6))))
})
proxy1 <- dataTableProxy('Table1')
observeEvent(input$Table1_cell_edit, {
Df <<- editData(Df, input$Table1_cell_edit, 'Table1', rownames = FALSE, resetPaging = FALSE)
})}
#run
shinyApp(ui = ui, server = server)
in my example app I have the user give some input and generate a data.table from it in the first tab. in the second tab I would like to show the plot, depending on the data.table. I am having quite a hard time to get the reactivity right. Unfortunately at this point I get the error: Operation not allowed without an active reactive context.
Please help me or give me hints what I am doing wrong.
the data:
tdata <- data.table(fruit = c("Apple", "Apple", "Apple", "Apple", "Apple", "Apple", "Apple", "Apple","Banana", "Banana","Banana","Banana","Banana", "Banana","Banana","Banana"),
Fertilizer = c(1,2,4,3,2,2,2,2,1,4,3,2,4,4,3,1),
amount = c(2,3,4,7,1,34,33,21,12,32,22,17,14,9,22,6),
red = rep(c("+","+","-","-"),4),
green = rep(c("+","-"),8))
tdata[, grp := do.call(paste, c(list(sep="\n"),.SD)),.SDcols = 4:5]
UI:
library(shiny)
library(data.table)
library(DT)
ui <- (fluidPage(tagList(
sidebarLayout(
sidebarPanel(uiOutput("file_input")),
mainPanel(
tabsetPanel(
tabPanel("Data",dataTableOutput('fruit_table') ),
tabPanel("Plot", plotOutput('barPlot'))
))))))
Server:
server <- function(input, output) {
fileData <- reactive(
return(tdata)
)
output$file_input <- renderUI ({
if(is.null(fileData())){
return()
}else{
tagList(
checkboxGroupInput(inputId = "fruit",
label = "fruit",
choices = c(unique(fileData()[,get("fruit")])),
selected = fileData()[1, 1, with = FALSE]),
checkboxGroupInput(inputId = "tube",
label = "Fertilizer",
choices = unique(fileData()[,get("Fertilizer")]),
selected = fileData()[1, 3, with = F]),
###build checkboxes from Loop:
lapply(1:(length(fileData())-4), function(i) {
checkboxGroupInput(inputId = paste0("color",i),
label = colnames(fileData()[,i+3, with = FALSE]),
choices = c(unique(fileData()[,get(colnames(fileData()[,i+3, with = FALSE]))])),
inline = TRUE,
selected = fileData()[1, i+3, with = FALSE])
}))}})
output$fruit_table <- renderDataTable({
if(is.null(fileData())){
return(NULL)
}else{
validate(
need(input$fruit, 'Check at least one fruit'),
need(input$tube, 'Check at least one Fertilizer'),
####loop not working in here
need(input$color1, "Check at least one !"),
need(input$color2, "Check at least one !")
)
filter_expr <- TRUE
if (!(is.null(input$fruit))) {
filter_expr <- filter_expr & fileData()[,fruit] %in% input$fruit
#print((input$fruit))
}
if (!(is.null(input$tube))) {
filter_expr <- filter_expr & fileData()[,Fertilizer] %in% input$tube
}
##non-loop-verison
if (!(is.null(input$color1))) {
filter_expr <- filter_expr & fileData()[,red] %in% input$color1
}
if (!(is.null(input$color2))) {
filter_expr <- filter_expr & fileData()[,green] %in% input$color2
}
datatable(fileData()[filter_expr,],options = list(pageLength = 25))
}})
plot.dat <- reactiveValues(main = NULL)
plot.dat$main <- ggplot(data = fileData(), mapping = aes( x = fileData()[,grp], y =fileData()[,amount]))+
geom_boxplot( stat = 'boxplot',
position = position_dodge(width=0.8),
width = 0.55)
observe({
output$barPlot <- renderPlot({
if(is.null(fileData())){
return(NULL)
}else{
validate(
need(input$fruit, 'Check at least one fruit'),
need(input$tube, 'Check at least one Fertilizer'),
need(input$color1, "Check at least one !"),
need(input$color2, "Check at least one !")
)
plot.dat$main
}})
})
}
shinyApp(ui = ui, server = server
)
You need to update the data that gets plotted. See the following working code. I extracted the data to filter in a reactive expression myFilter. This needs to be called before creating the table as well as before creating the plot.
library(shiny)
library(data.table)
library(DT)
library(ggplot2)
tdata <- data.table(fruit = c("Apple", "Apple", "Apple", "Apple", "Apple", "Apple", "Apple", "Apple","Banana", "Banana","Banana","Banana","Banana", "Banana","Banana","Banana"),
Fertilizer = c(1,2,4,3,2,2,2,2,1,4,3,2,4,4,3,1),
amount = c(2,3,4,7,1,34,33,21,12,32,22,17,14,9,22,6),
red = rep(c("+","+","-","-"),4),
green = rep(c("+","-"),8))
tdata[, grp := do.call(paste, c(list(sep="\n"),.SD)),.SDcols = 4:5]
ui <- (fluidPage(tagList(
sidebarLayout(
sidebarPanel(uiOutput("file_input")),
mainPanel(
tabsetPanel(
tabPanel("Data",dataTableOutput('fruit_table') ),
tabPanel("Plot", plotOutput('boxPlot'))
))))))
server <- function(input, output) {
fileData <- tdata # static data, doesn't change, noneed to be reactive
output$file_input <- renderUI ({
validate(need(!is.null(fileData), ''))
tagList(
checkboxGroupInput(inputId = "fruit",
label = "fruit",
choices = c(unique(fileData[,get("fruit")])),
selected = fileData[1, 1, with = FALSE]),
checkboxGroupInput(inputId = "tube",
label = "Fertilizer",
choices = unique(fileData[,get("Fertilizer")]),
selected = fileData[1, 3, with = F]),
###build checkboxes from Loop:
lapply(seq(length(fileData)-4), function(i) {
checkboxGroupInput(inputId = paste0("color",i),
label = colnames(fileData[,i+3, with = FALSE]),
choices = c(unique(fileData[,get(colnames(fileData[,i+3, with = FALSE]))])),
inline = TRUE,
selected = fileData[1, i+3, with = FALSE])
})
)
})
# build a filter according to inputs
myFilter <- reactive({
validate(need(!is.null(fileData), ''))
validate(
need(input$fruit, 'Check at least one fruit'),
need(input$tube, 'Check at least one Fertilizer'),
need(input$color1, "Check at least one !"),
need(input$color2, "Check at least one !")
)
fileData[,fruit] %in% input$fruit & fileData[,Fertilizer] %in% input$tube &
fileData[,red] %in% input$color1 & fileData[,green] %in% input$color2
})
# print the datatable matching myFilter()
output$fruit_table <- renderDataTable({
datatable(fileData[myFilter(),],options = list(pageLength = 25))
})
# build a boxPLot according to myFilter()
output$boxPlot <- renderPlot({
validate(
need(!is.null(fileData), ''),
need(input$fruit, 'Check at least one fruit'),
need(input$tube, 'Check at least one Fertilizer'),
need(input$color1, "Check at least one !"),
need(input$color2, "Check at least one !")
)
data <- fileData[myFilter(),]
ggplot(data = data, mapping = aes( x = data[,grp], y =data[,amount]))+
geom_boxplot( stat = 'boxplot',
position = position_dodge(width=0.8),
width = 0.55)
})
}
shinyApp(ui = ui, server = server)