Struggling to setup multi-step SelectInput in Shiny - r

You can see I'm trying to setup three different filters, all of them reactive to each other, but it keeps giving me errors when I test it out.
What I'm trying to accomplish is setting up these filters in a way where say I choose Territory 1, it'll only give me the corresponding states (Ohio) and cities (Columbus and Cleveland). At the same time, say I select both Territory 1 and 4, it will give me the states (Ohio and Michigan) and the cities (Columbus, Cleveland, and Grand Rapids, but not Detroit). At the same time, say I wanted to ignore the territory field and decided to just go right to the State filter, it will give me all the state options (keeping in mind, I haven't chosen anything in territory yet). Same thing if I just wanted to go right to the city filter.
So basically, I want all the filters to be reactively tied to each other, but with no rigid hierarchy where I have to choose territory first, then state, and finally cities.
Was I able to explain that well?
Here is the code.
The setup:
library(shiny)
library(dplyr)
library(highcharter)
df <- structure(list(territory = structure(c(1L, 1L, 2L, 2L, 3L, 4L
), .Label = c("1", "2", "3", "4"), class = "factor"), state = structure(c(3L,
3L, 1L, 1L, 2L, 2L), .Label = c("Indiana", "Michigan", "Ohio"
), class = "factor"), city = structure(c(2L, 1L, 6L, 4L, 3L,
5L), .Label = c("Cleveland", "Columbus", "Detroit", "Gary", "Grand Rapids",
"Indianapolis"), class = "factor"), sales = 5:10, leads = 11:16), class = "data.frame", row.names = c(NA,
-6L)) %>%
mutate_all(as.character)
ui <- {
fluidPage(
fluidRow(
selectizeInput(
inputId = 'selectTerritory',
label = 'Select Territory',
choices = c('All Territories', sort(unique(df$territory))),
multiple = TRUE,
selected = 'All Territories'),
uiOutput(
outputId = 'selectState'),
uiOutput(
outputId = 'selectCity'),
highchartOutput("test")
# plotOutput()
)
)
}
server <- function(input, output, session) {
output$selectState <- renderUI({
# if 'All Territories' is not selected, then filter df by selected Territories. Otherwise, just get all states.
if (!('All Territories' %in% input$selectTerritory)) {
df <- df %>%
filter(
territory %in% input$selectTerritory)
}
states <- sort(unique(df$state))
selectizeInput(
inputId = 'selectState',
label = 'Select State',
choices = c('All States', states),
multiple = TRUE,
selected = 'All States')
})
output$selectCity <- renderUI({
# same strategy
if (!('All States' %in% input$selectState)) {
df <- df %>%
filter(
state %in% input$selectState,
territory %in% input$selectTerritory)
} else {
df <- df %>%
filter(
territory %in% input$selectTerritory)
}
cities <- sort(unique(df$city))
selectizeInput(
inputId = 'selectCity',
label = 'Select City',
choices = c('All Cities', cities),
multiple = TRUE,
selected = 'All Cities')
})
geog <- reactive({
res <- df %>% filter(is.null(input$selectTerritory) | territory %in% input$selectTerritory,
is.null(input$selectState) | state %in% input$selectState,
is.null(input$selectCity) | city %in% input$selectCity)
})
output$test <- renderHighchart({
res <- geog() %>% select_all()
graph <- res %>% group_by_all() %>% summarise(totals=sum(sales))
highchart() %>% hc_add_series(data = graph, type = "bar", hcaes(y = totals),
showInLegend = TRUE) %>% hc_add_theme(hc_theme_flat())
})
}
shinyApp(ui, server)

Firstly, I prefer single-file Shiny apps (it's much easier to copy/paste the whole app rather than having separate files for ui and server).
The other benefit of single-file Shiny apps is that when you post the code to something like Stack Overflow, you can just copy and paste the whole thing, including your calls to library() which are very important! For example, I don't know what package you are using for highChartOutput(), so including those library() calls makes it easier when people are reproducing your code (and becomes the default if you just have a single-file Shiny app). On a related note, your question doesn't really have anything to do with a plot output, so I'm ignoring that part here (you should be able to access the outputs the same way you normally would in a Shiny app).
Secondly, I wouldn't use rbind() to make a df (it can do weird things with the data types). You can just define your variable columns, and directly call data.frame() (e.g., df <- data.frame(v1,v2)). I created the reproducible data by defining your variable columns, calling data.frame, and then running dput(df), which means we can define your dataframe in a single call (and so it's easy to reproduce and avoid any typos).
Thirdly, I'm using dplyr::mutate_all(as.character) because I don't know the package you use for this (taRifx).
Finally, to answer your question... The way I would approach this problem is to define extra variables to each class corresponding effectively to 'All variables', and have these selected as the default for each selectizeInput.
library(shiny)
library(dplyr)
df <- structure(list(territory = structure(c(1L, 1L, 2L, 2L, 3L, 4L
), .Label = c("1", "2", "3", "4"), class = "factor"), state = structure(c(3L,
3L, 1L, 1L, 2L, 2L), .Label = c("Indiana", "Michigan", "Ohio"
), class = "factor"), city = structure(c(2L, 1L, 6L, 4L, 3L,
5L), .Label = c("Cleveland", "Columbus", "Detroit", "Gary", "Grand Rapids",
"Indianapolis"), class = "factor"), sales = 5:10, leads = 11:16), class = "data.frame", row.names = c(NA,
-6L)) %>%
mutate_all(as.character)
ui <- {
fluidPage(
fluidRow(
selectizeInput(
inputId = 'selectTerritory',
label = 'Select Territory',
choices = c('All Territories', sort(unique(df$territory))),
multiple = TRUE,
selected = 'All Territories'),
uiOutput(
outputId = 'selectState'),
uiOutput(
outputId = 'selectCity')
# plotOutput()
)
)
}
server <- function(input, output, session) {
output$selectState <- renderUI({
# if 'All Territories' is not selected, then filter df by selected Territories. Otherwise, just get all states.
if (!('All Territories' %in% input$selectTerritory)) {
df <- df %>%
filter(
territory %in% input$selectTerritory)
}
states <- sort(unique(df$state))
selectizeInput(
inputId = 'selectState',
label = 'Select State',
choices = c('All States', states),
multiple = TRUE,
selected = 'All States')
})
output$selectCity <- renderUI({
# same strategy
if (!('All States' %in% input$selectState)) {
df <- df %>%
filter(
state %in% input$selectState,
territory %in% input$selectTerritory)
} else {
df <- df %>%
filter(
territory %in% input$selectTerritory)
}
cities <- sort(unique(df$city))
selectizeInput(
inputId = 'selectCity',
label = 'Select City',
choices = c('All Cities', cities),
multiple = TRUE,
selected = 'All Cities')
})
}
shinyApp(ui, server)

Related

Is there a way to use pickerGroup (or selectizeGroup) module from shinyWidget on reactive data?

I have a Shiny app where I have a first Selectizegroup module in the sidebar that filter my data on 3 variables. I want to put a second selectize or pickergroup module in a tabpanel to produce some plot with the data filtered on supplementary 2 variables. But I found no way to apply the pickerGroup module on the reactive data obtained with the first group module.
I already tried to achieve it with isolate(), update(), observeEvent(), but I always failed....
A minimal example of my database:
base <- structure(list(annee = c(2017, 2018, 2017, 2016, 2018, 2017,
2017, 2018, 2018, 2016),
code_composante = structure(c(2L, 1L,2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L),
.Label = c("APS", "FSI"),
class = "factor"),
code_etape = structure(c(25L, 26L, 21L, 28L, 16L, 16L, 12L, 13L, 21L, 28L),
.Label = c("EP3CHE", "EP3EEE", "EP3GCE", "EP3INE", "EP3MAE", "EP3MEE", "EP3PHE", "EP40EE", "EP40GE", "EP40IE", "EP40KE", "EPCHIE", "EPCHSE", "EPEEAE", "EPGCCE", "EPINFE", "EPMACE", "EPMASE", "EPMATE", "EPMECE", "EPMIAE", "EPPHPE", "EPPHSE", "EPSDTE", "EPSDVE", "SP3SCE", "SP40PE", "SPAPSE"),
class = "factor"),
particularite = structure(c(3L,1L, 3L, 3L, 3L, 3L, 3L, 4L, 3L, 3L),
.Label = c("3LA", "4LA","Classique", "Parcours spécial"),
class = "factor"),
origine_gen2 = structure(c(1L, 3L, 3L, 4L, 4L, 3L, 4L, 1L, 3L, 3L),
.Label = c("Bacheliers antérieurs", "Flux latéral", "Néo-bacheliers", "Redoublement ", "Réorientation "),
class = "factor"),
code_resultat = structure(c(2L, 4L, 2L, 3L, 4L, 3L, 3L, 4L, 4L, 1L),
.Label = c("Admis", "Ajourné","Défaillant / démissionnaire", "Donnée manquante", "Réorientation (à affiner)"), class = "factor"),
poursuite = structure(c(4L, 3L, 4L,6L, 3L, 6L, 4L, 3L, 3L, 2L),
.Label = c("Année supérieure - Flux latéral","Année supérieure - Flux normal", "Non déterminé", "Redoublement", "Réorientation", "Sortie UPS - Echec", "Sortie UPS - Réussite" ),
class = "factor")),
class = c("tbl_df", "tbl", "data.frame" ),
row.names = c(NA, -10L))
And a little piece of the shiny app:
# contenu global ####
ui <- shinydashboard::dashboardPage(
shinydashboard::dashboardHeader(title = "Devenir et réussite en L1",
titleWidth = 300),
# shiny::uiOutput("logout_button")),
shinydashboard::dashboardSidebar(tags$head(tags$style(HTML(".sidebar { position: fixed; width: 300px;}" ))),
width = 300,
div(h1("Filtres", style = "margin-left: 10px;")),
shinyWidgets::selectizeGroupUI(id = "filterset",
btn_label = "Remettre les filtres à zéro",
inline = FALSE,
params = list(
annee = list(inputId = "annee", title = "Année"),
composante = list(inputId = "code_composante", title = "Code composante"),
particularite = list(inputId = "particularite", title = "Type de L1"),
etape = list(inputId = "code_etape", title = "Code étape")))),
shinydashboard::dashboardBody(
#### onglet "tables" ####
shiny::tabsetPanel(id = "tabset",
shiny::tabPanel(title = "Tables des flux",
shiny::fluidRow(shinydashboard::box(width = 4,
title = "Origine des étudiants",
DT::DTOutput("table_origine")))),
#### onglet "flowchart"####
shiny::tabPanel(title = "Flow chart",
shinydashboard::box(width = 12,
shinyWidgets::pickerGroupUI(id = "filterset_flowchart",
btn_label = "Remettre les filtres à zéro",
params = list(
origine = list(inputId = "origine_gen2", title = "Origine"),
resultat = list(inputId = "code_resultat", title = "Résultat")))),
shinydashboard::box(width = 12, height = "700px", shiny::plotOutput("flowchart"))
))))
####SERVER####
server <- function(input, output, session) {
#first filter
filtered_data <- callModule(
module = shinyWidgets::selectizeGroupServer,
id = "filterset",
data = base ,
vars = c("annee", "code_composante", "particularite", "code_etape")
)
# box_origine ####
output$table_origine <- DT::renderDT({
effectif_origine <- filtered_data() %>%
dplyr::select(origine_gen2) %>%
dplyr::group_by(origine_gen2) %>%
dplyr::count()
DT::datatable(effectif_origine,
selection = 'single')
})
# flowchart ####
filtered_flowchart_data <- callModule(
module = shinyWidgets::pickerGroupServer,
id = "filterset_flowchart",
data = filtered_data() %>%
droplevels()%>%
dplyr::mutate_if(is.factor, as.character),
vars = c("origine_gen2", "code_resultat")
)
output$flowchart <- shiny::renderPlot({
actualized_data <- filtered_flowchart_data() %>%
dplyr::mutate_if(is.character, as.factor) %>%
dplyr::group_by(poursuite) %>%
dplyr::count()%>%
dplyr::ungroup()
pie_chart <- pie(actualized_data$n, labels = actualized_data$poursuite)
})}
shiny::shinyApp(ui, server)
In the 2nd tabPannel ("Flow chart") I would like the pickerGroup (filtered_flowchart_data) to work on the filtered data from the selectizeGroup (filtered_data()) from the sidebar but without affecting the data of other tabpanels of course :)
With the version provided of my code I obtain a message
Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context.
I think there is possibly a way with a combination of observeEvent, update reactive and isolate but I didn't achieve it....
You can call the module inside a reactive conductor:
filtered_flowchart_data <- reactive({
x <- callModule(
module = shinyWidgets::pickerGroupServer,
id = "filterset_flowchart",
data = filtered_data() %>%
droplevels() %>%
dplyr::mutate_if(is.factor, as.character),
vars = c("origine_gen2", "code_resultat")
)
x()
})
If there's an issue you can also try
filtered_flowchart_data <- reactive({
callModule(
module = shinyWidgets::pickerGroupServer,
id = "filterset_flowchart",
data = filtered_data() %>%
droplevels() %>%
dplyr::mutate_if(is.factor, as.character),
vars = c("origine_gen2", "code_resultat")
)
})
and then you get the data by doing filtered_flowchart_data()().
Thanks for your answer Stéphane, the 2nd proposition achieves the job!
filtered_flowchart_data <- reactive({
callModule(
module = shinyWidgets::pickerGroupServer,
id = "filterset_flowchart",
data = filtered_data() %>%
droplevels() %>%
dplyr::mutate_if(is.factor, as.character),
vars = c("origine_gen2", "code_resultat")
)})
and get the data by using :
filtered_flowchart_data()()
I don't know if its very clean, I never used or saw double ()(), but the result is perfect :)

How to display multiple rows in the table from plotly graphs using R Shiny and delete the last row from the table?

I'm trying to plot the graph using plotly where the person clicks on the geom_points and it should populate that geom_point row into the rendered table below.
I have been successful in doing that. So Person clicks on the geom_point on the graph, that geom_point data (row) gets displayed. Now I am trying to append multiple rows to the same table instead of overwriting the previous row the person selected. Basically, I want the person should click on the multiple geom_points and the table should show all the geom_points data instead of overwriting the previous one.
library(shiny)
library(plotly)
library(DT)
d1=structure(list(Topic = c("compensation", "manager", "benefits",
"family", "communication", "worklifebalance", "perks", "compensation",
"benefits", "manager", "communication", "worklifebalance", "family",
"perks", "benefits", "compensation", "manager", "communication",
"family", "worklifebalance", "perks"),
variable = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L),
.Label = c("Prct", "Count"), class = "factor"),
value = c(2.23121245555964, 0.723305136692411, 0.576192227534633,
0.202280250091946, 0.190020840995464, 0.153242613706019,
0.0122594090964816, 0.913705583756345, 0.609137055837563,
0.50761421319797, 0.50761421319797, 0.304568527918782, 0.203045685279188,
0, 1.49977276170277, 1.21193758521436, 0.893803969095592,
0.439327374640206, 0.348432055749129, 0.242387517042872,
0.0757460990758976),
group = c("APAC", "APAC", "APAC", "APAC", "APAC", "APAC", "APAC",
"EMEA", "EMEA", "EMEA", "EMEA", "EMEA", "EMEA", "EMEA",
"AMERICAS", "AMERICAS", "AMERICAS", "AMERICAS", "AMERICAS",
"AMERICAS", "AMERICAS")),
.Names = c("Topic", "variable", "value", "group"), class = c("data.table", "data.frame"),
row.names = c(NA, -21L))
ui <- fluidPage(
fluidRow(plotlyOutput('keywords')),
fluidRow(verbatimTextOutput("selection")),
fluidRow(DT::dataTableOutput("table1"))
)
d0 = d1
key <- row.names(d0)
server = function(input,output){
output$keywords = renderPlotly({
d0 <- data.frame(d0, key)
p = ggplot(d0, aes(reorder(Topic,-value), value, key = key)) +
geom_point(aes(colour = value),
shape = 16,
size = 3,
show.legend = F) +
facet_wrap(~ group)+
theme_minimal()
ggplotly(p)
})
output$selection <- renderPrint({
s <- event_data("plotly_click")
cat("You selected: \n\n")
data.frame(s)
})
selection2 <- reactive({
s <- event_data("plotly_click")
cat("You selected: \n\n")
df <- data.frame(s)
})
output$table1 = renderDT({
d2 <- d1 %>% filter(key == selection2()$key)
d2
})
}
shinyApp(ui, server)
if you will run this code and click on points. you will notice that it overwrites the rows in the table. I expect that it should keep appending the rows when you keep clicking on points. I am new to using shiny but if there is a way to do use reactiveValues or observeEvent or anything else?
You have to update value of d2 so you can do it with reactiveVal(). Here's what I've changed in yours server function:
d2 <- reactiveVal(data.frame())
observeEvent(event_data("plotly_click"), {
d2Temp <- rbind(
d2(),
d1 %>% filter(key == selection2()$key)
)
d2(d2Temp)
})
output$table1 = renderDT({
d2()
})
Firstly you must initialise d2 reactive value with an empty data.frame. Then, observe "plotly_click" and bind new row to old data.frame. Lastly update value of yours reactiveVal with d2(d2Temp).

Shiny reactive outputs are not updating as expected

I have created a shiny app that pulls software components and their versions off of a list of nodes. The goal here is to make all of our nodes consistent when possible and this app helps us see which nodes are inconsistent.
Currently you can modify the version in the 'baseline' handsontable and it will reactively update the pivot table below with the change as well as the BaselineStats column within the handsontable. This works as expected. I have been asked to add the ability to upload a csv file that would overwrite the baseline table so a user does not have to change these 'baseline' versions each time they load the app.
In addition, there are some components that are 100% consistent. Currently those do not appear in the 'baseline' handsontable (since this is a tool to show inconsistency) but I have added a checkbox so that the user can still report on those components that are 100% consistent.
For some reason neither the fileUpload nor the checkboxInput are updating and no matter how much I poke and prod at my code, I cannot figure out why.
server.R
library(shiny)
library(rhandsontable)
library(rpivotTable)
library(dplyr)
library(stringr)
library(lubridate)
shinyServer(function(input, output) {
# Create dataframe
df.consistency <- structure(list(Node = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L,
2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L), .Label = c("A", "B", "C",
"D"), class = "factor"), Component = structure(c(3L, 4L, 1L, 2L, 3L,
4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L), .Label = c("docker.version",
"kernel.version", "os.name", "os.version"), class = "factor"),
Version = structure(c(10L, 3L, 1L, 6L, 10L, 3L, 1L, 7L, 10L,
5L, 1L, 8L, 10L, 4L, 2L, 9L), .Label = c("1.12.1", "1.13.1",
"16.04", "17.04", "18.04", "3.10.0", "3.11.0", "3.12.0",
"3.13.0", "RedHat"), class = "factor")), class = "data.frame", row.names = c(NA,
-16L))
# Get Date Time
Report.Date <- Sys.Date()
df.baseline <- reactive({
inputFile <- input$uploadBaselineData
if(!is.null(inputFile)){
read.csv(inputFile$datapath, header = input$header)
} else{
if(input$showConsistent == FALSE){
# Count the number of occurrences for Version and Component, then remove the Components that are consistent (not duplicated => nn == 1) and then remove nn column
df.clusterCons.countComponent <- df.consistency %>%
add_count(Version, Component) %>%
add_count(Component) %>%
filter(nn > 1) %>%
select(-nn)
# Change back to dataframe after grouping
df.clusterCons.countComponent <- as.data.frame(df.clusterCons.countComponent)
# Components and Versions are shown for every node/cluster.
# Reduce this df to get only a unique Component:Version combinations
df.clusterCons.dist_tbl <- df.clusterCons.countComponent %>%
distinct(Component, Version, .keep_all = TRUE)
#Create a df that contains only duplicated rows (rows that are unique i.e. versions are consistent, are removed)
df.clusterCons.dist_tbl.dup <- df.clusterCons.dist_tbl %>%
filter(Component %in% unique(.[["Component"]][duplicated(.[["Component"]])]))
#Create a baseline df to be used to filter larger dataset later
#(baseline = max(n) for Version -- but must retain Component since that is the parameter we will use to filter on later)
df.clusterCons.baseline <- df.clusterCons.dist_tbl.dup[order(df.clusterCons.dist_tbl.dup$Component, df.clusterCons.dist_tbl.dup$n, decreasing = TRUE),]
df.clusterCons.baseline <- df.clusterCons.baseline[!duplicated(df.clusterCons.baseline$Component), ]
df.clusterCons.baseline <- df.clusterCons.baseline %>%
select(Component, Version)
}
else{
# Count the number of occurrences for Version and Component, then remove the Components that are consistent (not duplicated => nn == 1) and then remove nn column
df.clusterCons.countComponent <- df.consistency %>%
add_count(Version, Component) %>%
add_count(Component) %>%
select(-nn)
# Change back to dataframe after grouping
df.clusterCons.countComponent <- as.data.frame(df.clusterCons.countComponent)
# Components and Versions are shown for every node/cluster.
# Reduce this df to get only a unique Component:Version combinations
df.clusterCons.dist_tbl <- df.clusterCons.countComponent %>%
distinct(Component, Version, .keep_all = TRUE)
df.clusterCons.baseline <- df.clusterCons.dist_tbl[order(df.clusterCons.dist_tbl$Component, df.clusterCons.dist_tbl$n, decreasing = TRUE),]
df.clusterCons.baseline <- df.clusterCons.baseline[!duplicated(df.clusterCons.baseline$Component), ]
df.clusterCons.baseline <- df.clusterCons.baseline %>%
select(Component, Version)
}
}
})
df.componentVersionCounts <- df.consistency %>%
add_count(Component) %>%
rename("CountComponents" = n) %>%
add_count(Component, Version) %>%
rename("CountComponentVersions" = n) %>%
mutate("BaselineStats" = paste0("Baseline: ", round(CountComponentVersions / CountComponents * 100, 2), "% of Total: ", CountComponents)) %>%
select(Component, Version, BaselineStats) %>%
distinct(.keep_all = TRUE)
df.componentVersions_tbl <- reactive({
df.componentVersions_tbl <- df.baseline() %>%
distinct(Component, .keep_all = TRUE) %>%
select(Component, Version) %>%
left_join(df.componentVersionCounts, by = c("Component" = "Component", "Version" = "Version"))
})
# Report Date Output
output$reportDate <- renderText({
return(paste0("Report last run: ", Report.Date))
})
# handsontable showing baseline and allowing for an updated baseline
output$baseline_table <- rhandsontable::renderRHandsontable({
rhandsontable(df.componentVersions_tbl(), rowHeaders = NULL) %>%
hot_col("Component", readOnly = TRUE) %>%
hot_col("BaselineStats", readOnly = TRUE) %>%
hot_cols(columnSorting = TRUE) %>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE, filters = TRUE)
})
observe({
hot = isolate(input$baseline_table)
if(!is.null(input$baseline_table)){
handsontable <- hot_to_r(input$baseline_table)
df.clusterCons.baseline2 <- handsontable %>%
select(-BaselineStats)
df.componentVersions_tbl <- df.clusterCons.baseline2 %>%
left_join(df.componentVersionCounts, by = c("Component" = "Component", "Version" = "Version"))
output$baseline_table <- rhandsontable::renderRHandsontable({
rhandsontable(df.componentVersions_tbl, rowHeaders = NULL) %>%
hot_col("Component", readOnly = TRUE) %>%
hot_col("BaselineStats", readOnly = TRUE) %>%
hot_cols(columnSorting = TRUE) %>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE, filters = TRUE)
})
df.clusterIncons <- anti_join(df.consistency, handsontable, by = c("Component" = "Component", "Version" = "Version"))
df.clusterIncons <- df.clusterIncons
# Pivot Table showing data with inconsistencies
output$pivotTable <- rpivotTable::renderRpivotTable({
rpivotTable::rpivotTable(df.clusterIncons, rows = c("Cluster", "Node"), cols = "Component", aggregatorName = "List Unique Values", vals = "Version",
rendererName = "Table",
inclusions = list(Component = list("os.version", "os.name", "kernel.version", "docker.version")))
})
output$downloadBaselineData <- downloadHandler(
filename = function() {
paste('baselineData-', Sys.Date(), '.csv', sep='')
},
content = function(file) {
baseline_handsontable <- handsontable %>%
select(-BaselineStats)
write.csv(baseline_handsontable, file, row.names = FALSE)
}
)
output$downloadPivotData <- downloadHandler(
filename = function() {
paste('pivotData-', Sys.Date(), '.csv', sep='')
},
content = function(file) {
write.csv(df.clusterIncons, file, row.names = FALSE)
}
)
}
})
})
ui.R
library(shiny)
library(shinydashboard)
library(rhandsontable)
library(rpivotTable)
dashboardPage(
dashboardHeader(title = "Test Dashboard", titleWidth = "97%"),
dashboardSidebar(
collapsed = TRUE,
sidebarMenu(
menuItem("App", tabName = "app", icon = icon("table"))
)
),
dashboardBody(
tabItems(
tabItem("app",
fluidRow(
box(width = 3, background = "light-blue",
"This box includes details to the user about how the application works", br(), br(), br(),
verbatimTextOutput("reportDate")
),
box(width = 7, status = "info", title = "Version baselines based on greatest occurance",
rHandsontableOutput("baseline_table", height = "350px")
),
column(width = 2,
fluidRow(
fileInput("uploadBaselineData", "Upload Other Baseline Data:", multiple = FALSE,
accept = ".csv")
),
fluidRow(
downloadButton("downloadBaselineData", "Download Baseline Data")
),
br(),
fluidRow(
downloadButton("downloadPivotData", "Download Pivot Table Data")
),
br(),
fluidRow(
checkboxInput("showConsistent", "Show Consistent Components in baseline")
)
)
),
fluidRow(
box(width = 12, status = "info", title = "Nodes with versions inconsistent with baseline",
div(style = 'overflow-x: scroll', rpivotTable::rpivotTableOutput("pivotTable", height = "500px"))
)
)
)
)
)
)
I have worked with reactivity quite often but I do not frequently use observe or isolate so that may be where I am running into an issue. I did also try out the new reactlog package but I am still not sure of a path forward.
Here is a picture of the reactlog output before I click the check box or upload new baseline data:
And after:
Actually the given structure of the Shiny App is very tangled and it does not use reactivity efficiently. So first we can start with a simpler app to make sure the basic components are working, then add more.
Some of the problems
the included dataframe df.consistency interferes with the real reactive components you want to add. For instance, the if/else flow is problematic because it always jumps to the first else since the csv does not exist when the app is launched and the expression to read it is not accurate, however df.consistency is always available.
there is duplication of the same component like output$baseline_table which is defined twice.
with read.csv, you passed an argument header = input$header which is not defined (if you took this from the example here, it refers to the checkbox, but it is not valid here).
Minimal app
If you want to start with a minimal app, you can start with the following code. This will allow you to:
use default data or upload a csv to override the default.
view the results in the rhandsontable in the middle.
Notice that:
baseline_data is reactive, that's why the other expressions that use it are also reactive.
if you want to have different calculations of df.componentVersionCounts depending on the checkbox, you can add the if/else inside the expression to write the calculations for both cases.
library(shiny)
library(rpivotTable)
library(dplyr)
library(stringr)
library(lubridate)
library(shinydashboard)
library(rhandsontable)
## UI ------------------------------------------------------------------------------
ui <- dashboardPage(
dashboardHeader(title = "Test Dashboard", titleWidth = "97%"),
dashboardSidebar(
collapsed = TRUE,
sidebarMenu(
menuItem("App", tabName = "app", icon = icon("table"))
)
),
dashboardBody(
tabItems(
tabItem("app",
fluidRow(
box(width = 3, background = "light-blue",
"This box includes details to the user about how the application works", br(), br(), br(),
verbatimTextOutput("reportDate")
),
box(width = 7, status = "info", title = "Version baselines based on greatest occurance",
rHandsontableOutput("baseline_table", height = "350px")
),
column(width = 2,
fluidRow(
fileInput("uploadBaselineData", "Upload Other Baseline Data:", multiple = FALSE,
accept = ".csv")
),
fluidRow(
checkboxInput("showConsistent", "Show Consistent Components in baseline")
)
)
)
)
)
)
)
## define default baseline data
df.consistency <- structure(list(Node = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L,
2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L),
.Label = c("A", "B", "C",
"D"), class = "factor"), Component = structure(c(3L, 4L, 1L, 2L, 3L,
4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L), .Label = c("docker.version",
"kernel.version", "os.name", "os.version"), class = "factor"),
Version = structure(c(10L, 3L, 1L, 6L, 10L, 3L, 1L, 7L, 10L,
5L, 1L, 8L, 10L, 4L, 2L, 9L),
.Label = c("1.12.1", "1.13.1",
"16.04", "17.04", "18.04", "3.10.0", "3.11.0", "3.12.0",
"3.13.0", "RedHat"), class = "factor")), class = "data.frame", row.names = c(NA,
-16L))
## Server ------------------------------------------------------------------
server <- function(input, output) {
## Get Date Time
Report.Date <- Sys.Date()
baseline_data <- reactive({
inputFile <- input$uploadBaselineData
if(!is.null(inputFile)){
## WHEN A CSV IS UPLOADED
read.csv(inputFile$datapath)
}else{
## DEFAULT
df.consistency #or write the any other expression to read from a certain path or query
}
})
## df.componentVersionCounts ---------------------------------------------------------------
df.componentVersionCounts <- reactive({
req(baseline_data())
baseline_data() %>%
add_count(Component) %>%
rename("CountComponents" = n) %>%
add_count(Component, Version) %>%
rename("CountComponentVersions" = n) %>%
mutate("BaselineStats" = paste0("Baseline: ", round(CountComponentVersions / CountComponents * 100, 2), "% of Total: ", CountComponents)) %>%
select(Component, Version, BaselineStats) %>%
distinct(.keep_all = TRUE)
})
## df.componentVersions_tbl ------------------------------------------------------------
df.componentVersions_tbl <- reactive({
req(baseline_data())
baseline_data() %>% ##df.baseline()
distinct(Component, .keep_all = TRUE) %>%
select(Component, Version) %>%
left_join(df.componentVersionCounts(),
by = c("Component" = "Component", "Version" = "Version"))
})
# handsontable showing baseline and allowing for an updated baseline ---------------------
output$baseline_table <- rhandsontable::renderRHandsontable({
rhandsontable(df.componentVersions_tbl(), rowHeaders = NULL) %>%
hot_col("Component", readOnly = TRUE) %>%
hot_col("BaselineStats", readOnly = TRUE) %>%
hot_cols(columnSorting = TRUE) %>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE, filters = TRUE)
})
# Report Date Output -------------------------------------------------------
output$reportDate <- renderText({
return(paste0("Report last run: ", Report.Date))
})
}
# Run the application
shinyApp(ui = ui, server = server)

Reorder reactive ggplot

I'm trying to reorder the x axis by the values in the y axis. The x axis is a name, the y axis is an integer. Both are reactive, user defined inputs. I have created a datatable that renders in the correct order, but ggplot does not take that order. Instead it does an alphabetical order.
My current code is:
Packages
library(shiny)
library(readxl) # to load the data into R
library(tidyverse)
library(stringr)
library(DT)
library(tools)
library(magrittr)
Data
lpop <-read.csv("londonpopchange.csv", header=TRUE)
UI
# Define UI for application that plots features of movies
ui <- fluidPage(
# Sidebar layout with a input and output definitions
sidebarLayout(
# Inputs
sidebarPanel(
# Select variable for y-axis
selectInput(inputId = "y",
label = "Y-axis:",
choices = c("Mid Year 2016" = "MYE2016",
"Births" = "Births",
"Deaths" = "Deaths",
"Births minus Deaths" = "BirthsminusDeaths",
"Internal Migration Inflow" = "InternalMigrationInflow",
"Internal Migration Outflow" = "InternalMigrationOutflow",
"Internal Migration Net" = "InternalMigrationNet",
"International Migration Inflow" = "InternationalMigrationInflow",
"International Migration Outflow" = "InternationalMigrationOutflow",
"International Migration Net" = "InternationalMigrationNet"),
selected = "MYE2016"),
# Select variable for x-axis
selectInput(inputId = "x",
label = "X-axis:",
choices = c("Borough" = "Name"),
selected = "Name")
),
# Output
mainPanel(
h1(textOutput("MainTitle")),
br(),
plotOutput(outputId = "geom_bar"),
DT::dataTableOutput("mytable")
)
)
)
Server
# Define server function required to create the scatterplot
server <- function(input, output) {
#this creates the title
output$MainTitle <- renderText({
paste(input$y, "for London Boroughs")
})
#creates a data table that reacts to the user variable input and arranges
#by the y variable
df <- reactive({
lpop %>%
select(input$x, input$y, "WF") %>%
arrange_(.dots = input$y) #%>%
# setNames(1:2, c("x", "y"))
})
#outputs the user defined data frame
output$mytable = ({DT::renderDataTable({df()})})
# Create the bar plot object the plotOutput function is expecting
output$geom_bar <- renderPlot({
ggplot(data = df(), aes_string(x = input$x, y = input$y, fill = "WF")) +
geom_bar(stat = "identity") +
scale_fill_manual(values=c("#000000", "#00D253")) +
theme(axis.text.x = element_text(angle = 90)) +
xlab(input$x)
})
}
# Create a Shiny app object
shinyApp(ui = ui, server = server)
It renders as so: https://jwest.shinyapps.io/ShinyPopulation/
If I use the reorder function in ggplot, it amalgamates all "Names" into one bar, see below.
# Create the bar plot object the plotOutput function is expecting
output$geom_bar <- renderPlot({
ggplot(data = df(), aes_string(x = reorder(input$x, input$y), y = input$y, fill = "WF")) +
geom_bar(stat = "identity") +
scale_fill_manual(values=c("#000000", "#00D253")) +
theme(axis.text.x = element_text(angle = 90)) +
xlab(input$x)
})
}
How can I render it by the Y axis? Is it something to do with scale_x_discrete(limits = ...). If it is I am confused as to how i'm meant to reference the first column of the reactive df
The csv can be downloaded here: https://drive.google.com/file/d/1QLT8CX9XFSx3WU_tADyWgyddHYd3-VSp/view?usp=sharing
DPUT
structure(list(Code = structure(c(7L, 1L, 12L, 13L, 14L), .Label = c("E09000001",
"E09000002", "E09000003", "E09000004", "E09000005", "E09000006",
"E09000007", "E09000008", "E09000009", "E09000010", "E09000011",
"E09000012", "E09000013", "E09000014", "E09000015", "E09000016",
"E09000017", "E09000018", "E09000019", "E09000020", "E09000021",
"E09000022", "E09000023", "E09000024", "E09000025", "E09000026",
"E09000027", "E09000028", "E09000029", "E09000030", "E09000031",
"E09000032", "E09000033"), class = "factor"), Name = structure(c(6L,
7L, 12L, 13L, 14L), .Label = c("Barking and Dagenham", "Barnet",
"Bexley", "Brent", "Bromley", "Camden", "City of London", "Croydon",
"Ealing", "Enfield", "Greenwich", "Hackney", "Hammersmith and Fulham",
"Haringey", "Harrow", "Havering", "Hillingdon", "Hounslow", "Islington",
"Kensington and Chelsea", "Kingston upon Thames", "Lambeth",
"Lewisham", "Merton", "Newham", "Redbridge", "Richmond upon Thames",
"Southwark", "Sutton", "Tower Hamlets", "Waltham Forest", "Wandsworth",
"Westminster"), class = "factor"), Geography = structure(c(1L,
1L, 1L, 1L, 1L), .Label = "London Borough", class = "factor"),
MYE2016 = c(249162L, 7246L, 273239L, 181783L, 272078L), Births = c(2671L,
68L, 4405L, 2446L, 3913L), Deaths = c(1180L, 38L, 1168L,
895L, 1140L), BirthsminusDeaths = c(1491L, 30L, 3237L, 1551L,
2773L), InternalMigrationInflow = c(22189L, 856L, 21271L,
19109L, 22469L), InternalMigrationOutflow = c(25132L, 792L,
23324L, 20488L, 29113L), InternalMigrationNet = c(-2943L,
64L, -2053L, -1379L, -6644L), InternationalMigrationInflow = c(11815L,
756L, 5054L, 5333L, 7480L), InternationalMigrationOutflow = c(6140L,
441L, 3534L, 4336L, 4460L), InternationalMigrationNet = c(5675L,
315L, 1520L, 997L, 3020L), Other = c(-24L, -1L, -14L, 46L,
-3L), Estimated.Population..mid.2017 = c(253361L, 7654L,
275929L, 182998L, 271224L), WF = structure(c(1L, 1L, 1L,
1L, 1L), .Label = c("London Borough", "Waltham Forest"), class = "factor")), .Names = c("Code",
"Name", "Geography", "MYE2016", "Births", "Deaths", "BirthsminusDeaths",
"InternalMigrationInflow", "InternalMigrationOutflow", "InternalMigrationNet",
"InternationalMigrationInflow", "InternationalMigrationOutflow",
"InternationalMigrationNet", "Other", "Estimated.Population..mid.2017",
"WF"), row.names = c(NA, 5L), class = "data.frame")

Trigger query based on selected date range in Shiny R

I have exctracted below mentioned dataframe in R using SQL query.
Query<-paste0("select ID, Date, Value, Result
From Table1
where date(date)>='2018-07-01'
and date(date)<='2018-08-31');")
Dev1<-dbgetquery(database,Query)
Dev1:
ID Date Value Result
KK-112 2018-07-01 15:37:45 ACR Pending
KK-113 2018-07-05 18:14:25 ACR Pass
KK-114 2018-07-07 13:21:55 ARR Accepted
KK-115 2018-07-12 07:47:05 ARR Rejected
KK-116 2018-07-04 11:31:12 RTR Duplicate
KK-117 2018-07-07 03:27:15 ACR Pending
KK-118 2018-07-18 08:16:32 ARR Rejected
KK-119 2018-07-21 18:19:14 ACR Pending
Using above mentioned dataframe, I have created below mentioned pivot dataframe in R.
Value Pending Pass Accepted Rejected Duplicate
ACR 3 1 0 0 0
ARR 0 0 1 2 0
RTR 0 0 0 0 0
And I just want a little help here to trigger those query based on a date range (for example, if one selects some date range on shiny dashboard, data gets automatically updated).
For the sake of simplicity, I have used only 4 columns of dataframe but in my original data I have 30 columns and it's not fitting in the frame on ui dashboard. Please suggest how to structure the table and color the header.
I am using below mentioned sample code to pass the dataframe.
library(shiny)
library(dplyr)
library(shinydashboard)
library(tableHTML)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
tableHTML_output("mytable")
)
)
server <- function(input, output) {
Date<-Dev1$Date
{
output$mytable <- render_tableHTML( {
Pivot<-data.table::dcast(Dev1, Value ~ Result, value.var="ID",
fun.aggregate=length)
Pivot$Total<-rowSums(Pivot[2:3])
Pivot %>%
tableHTML(rownames = FALSE,
widths = rep(80, 7))
})
}
}
shinyApp(ui, server)
Rrequired sample design:
Here's how you can do it -
library(shiny)
library(dplyr)
library(data.table)
library(shinydashboard)
library(tableHTML)
library(DT)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
dateRangeInput("dates", "Select Dates"),
actionButton("run_query", "Run Query"),
br(), br(),
tags$strong("Query that will be run when user hits above button"),
verbatimTextOutput("query"),
br(),
tableHTML_output("mytable"),
br(),
DTOutput("scrollable_table")
)
)
server <- function(input, output) {
Dev1 <- eventReactive(input$run_query, {
# Query <- sprintf("select ID, Date, Value, Result From Table1 where date(date) >= '%s' and date(date) <= '%s');",
# input$dates[1], input$dates[2])
# dbgetquery(database, Query)
structure(list(ID = c("KK-112", "KK-113", "KK-114", "KK-115",
"KK-116", "KK-117", "KK-118", "KK-119"),
Date = c("2018-07-01 15:37:45", "2018-07-05 18:14:25", "2018-07-07 13:21:55", "2018-07-12 07:47:05",
"2018-07-04 11:31:12", "2018-07-07 03:27:15", "2018-07-18 08:16:32",
"2018-07-21 18:19:14"),
Value = c("ACR", "ACR", "ARR", "ARR", "RTR", "ACR", "ARR", "ACR"),
Result = c("Pending", "Pass", "Accepted", "Rejected", "Duplicate", "Pending", "Rejected", "Pending")),
.Names = c("ID", "Date", "Value", "Result"),
row.names = c(NA, -8L), class = "data.frame")
})
output$mytable <- render_tableHTML({
req(Dev1())
Pivot <- data.table::dcast(Dev1(), Value ~ Result, value.var="ID",
fun.aggregate=length)
Pivot$Total <- rowSums(Pivot[, 2:6])
Pivot %>%
tableHTML(rownames = FALSE, widths = rep(80, 7)) %>%
add_css_header(., css = list(c('background-color'), c('blue')), headers = 1:7)
})
output$query <- renderPrint({
sprintf("select ID, Date, Value, Result From Table1 where date(date) >= '%s' and date(date) <= '%s');",
input$dates[1], input$dates[2])
})
output$scrollable_table <- renderDT({
data.frame(matrix("test", ncol = 30, nrow = 5), stringsAsFactors = F) %>%
datatable(options = list(scrollX = TRUE, paginate = F))
})
}
shinyApp(ui, server)
You would take dates as inputs using dateRangeInput() which feeds the query (commented out in my code) in Dev1. Live query is shown under verbatimTextOutput("query"). I have made Dev1 eventReactive meaning the data will be pulled only when user hits 'Run Query' button. This will allow user to set both, from and to, dates before running the query (useful if you are pulling lot of data). mytable will update whenever Dev1 updates.
Have also added color to tableHTML header.
For horizontally scroll-able table I'd recommend DT package as demonstrated under DTOutput("scrollable_table").
Hope this is what you were looking for.
Note: Make sure you sanitize Query to avoid any SQL injection possibilities. Basic google search should help with that.
You can add a sliderInput to let the user select the desired range of dates, and then make a reactive dataframe that'll subset data based on the user's selected range. I have used the sample data you provided, using minimum and maximum values of Date to assign the range for sliderInput.
library(shiny)
library(dplyr)
library(shinydashboard)
library(tableHTML)
library(DT)
structure(list(ID = structure(1:8, .Label = c("KK-112", "KK-113", "KK-114", "KK-115", "KK-116", "KK-117", "KK-118", "KK-119"),
class = "factor"),
Date = structure(c(17713, 17717, 17719, 17724, 17716, 17719, 17730, 17733),
class = "Date"),
Value = structure(c(1L, 1L, 2L, 2L, 3L, 1L, 2L, 1L), .Label = c("ACR", "ARR", "RTR"), class = "factor"),
Result = structure(c(4L, 3L, 1L, 5L, 2L, 4L, 5L, 4L), .Label = c("Accepted", "Duplicate", "Pass", "Pending", "Rejected"),
class = "factor")), class = "data.frame", row.names = c(NA, -8L))
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
# Add sliderInput for date - lets the user select a range of dates
sliderInput("dates.range",
"Dates:",
min = min(Dev1$Date),
max = max(Dev1$Date),
value = as.Date("2018-07-18"),
timeFormat="%Y-%m-%d")
),
dashboardBody(
tableHTML_output("mytable"),
dataTableOutput("mytable2")
)
)
server <- function(input, output) {
data.subsetted.by.date <- reactive({
# Subset data - select dates which are in the user selected range of dates
subset(Dev1, Date > min(Dev1$Date) & Date < input$dates.range)
})
# Output subsetted data as a DataTable
output$mytable2 <- renderDataTable(data.subsetted.by.date())
Date <- Dev1$Date
output$mytable <- render_tableHTML({
Pivot <- data.table::dcast(Dev1, Value ~ Result, value.var = "ID", fun.aggregate=length)
Pivot$Total <- rowSums(Pivot[2:3])
Pivot %>%
tableHTML(rownames = FALSE, widths = rep(80, 7))
})
}
shinyApp(ui, server)
You can see I have used renderDataTable and dataTableOutput from the DT package. These allow creating scroll-able tables for your shiny app.
For from - to data you can use dateRangeInput() and then use the input from there to filter your data.
For example:
in your UI:
dateRangeInput("ID", "Date", min = as.Date(min(Dev1$Date)), max = as.Date(max(Dev1$Date))
and then in Server:
Pivot <- Dev1 %>% filter(Date >= input$ID[1] & Date <= input$ID[2])
Did I understand your question correct?

Resources