Trying to reconcile logging verse textOutput in R Shiny reactive values - r

When building out a shiny application and testing management of reactive components I came across this oddity when trying to dynamically fill textOutputs based on a selection in another table. I am unable to show the correct value in the textOutput but the Shiny Logger would seem to indicate my value is correct.
UI ouput is the row number.
Shinylogger output is the accountID
POC developed from these resources:
Subset a Column of a dataframe stored as a reactive expression eventReactive
Returning a dataframe in a reactive function in R Shiny Dashboard
POC code below.
Looking to reconcile why the shinylogger value is different from the textOutput value in the UI.
library(shiny)
library(shinyEventLogger)
library(bs4Dash)
library(dplyr)
ui <- fluidPage(
fluidRow(
column(
width = 12,
div(
bs4Dash::bs4Card(
id = "searchtableCard",
title = "Search Results",
elevation = 3,
width = 12,
closable = FALSE,
DT::dataTableOutput("searchTable")
)
)
)
),
fluidRow(
textOutput("accountid")
)
)
server <- function(input, output){
shinyEventLogger::set_logging(
r_console = TRUE,
file = TRUE,
js_console = TRUE)
shinyEventLogger::set_logging_session()
searchresults <- data.frame(
accountID = c("12345", "54321"),
lastname = c("McDingus", "McKraken"),
phone1 = c("555-123-5432", "555-000-5432")
)
applications <- data.frame(
accountID = c("12345", "54321"),
firstname = c("Alison", "Angus"),
lastname = c("McDingus", "McKraken"),
veh_make = c("Dodge", "Honda"),
veh_model = c("Charger", "Civic")
)
searchreactive <- shiny::reactive({searchresults})
# Search Table Output
output$searchTable = DT::renderDataTable({
searchreactive()
},
extensions = "Responsive", filter = "bottom", selection = 'single'
)
shiny::observeEvent(input$searchTable_rows_selected, {
s = input$searchTable_rows_selected
selRow <- searchreactive()[s,]
id = selRow[[1]]
app_active <- applications %>%
filter(accountID == id)
shinyEventLogger::log_value(app_active$accountID)
output$accountid <- shiny::renderText(app_active$accountID)
})
}
# Run the application
shinyApp(ui = ui, server = server)

Related

How to reset selected rows in Shiny

I have a small rshiny app, in which i can select row in datatable and get values from first columns.
but how to quickly get rid of the selected rows and values without clicking on the row again?
also if you know what can be improved in this code, then write, I just started coding in R
# Define UI
ui <- fluidPage(
dataTableOutput('main_information'),
fluidRow(
column(8,verbatimTextOutput('selected_rows', placeholder = TRUE)),
fluidRow(
column(4,actionButton("reset", "RESET"))
)
)
)
# Define server function
server <- function(input, output,session) {
getScoreTable<-reactive({
db <- dbConnect(SQLite(), "path")
data <- dbGetQuery(
conn = db,
statement =
'...'
)
})
output$main_information <- renderDataTable(
getScoreTable(),
options = list(
pageLength = 5,
lengthMenu = list(c(5,10, 25, 50, 100),
c('5', '10', '25','50', '100'))
)
)
s<-reactiveValues(data= NULL)
output$selected_rows = renderPrint({
s = input$main_information_rows_selected
if (length(s)) {
cat('These values were selected:\n\n')
cat(getScoreTable()[s,1], sep = '\n')
}else{
cat('No value has been selected')
}
})
}
# Create Shiny object
shinyApp(ui = ui, server = server)
You can use a custom action button:
library(DT)
js <- "
function ( e, dt, node, config ) {
dt.rows().deselect();
}
"
datatable(
iris,
extensions = c("Buttons", "Select"),
selection = "none",
options = list(
"dom" = "Bfrtip",
"select" = TRUE,
"buttons" = list(
list(
"extend" = "collection",
"text" = "DESELECT",
"action" = JS(js)
)
)
)
)
This example works fine. If you have an issue in Shiny, please provide a minimal reproducible code, not using SQL.

How to reactively filter which columns of a datatable are displayed?

I am trying to build an interactive data table that changes the displayed columns based on filters chosen by the user. The aim is to have a user select the columns they want to see via a dropdown, which will then cause the datatable to display those columns only.
library(shinyWidgets)
library(DT)
ui <-
fluidPage(
fluidRow(
box(width = 4,
pickerInput(inputId = "index_picker",
label = "Select index/indices",
choices = c("RPI", "RPIX", "CPI", "GDP Deflator"),
selected = "RPI",
multiple = T
)
)
)
fluidRow(
box(DT::dataTableOutput("index_table"), title = "Historic Inflation Indices", width = 12,
solidHeader = T, status = "primary")
)
)
server <- function(input, output, session) {
df_filt <- reactive({
if({
input$index_picker == "RPI" &
!is.null()
})
df_index %>%
select(Period, RPI.YOY, RPI.INDEX)
else if({
input$index_picker == "RPIX"
})
df_index %>%
select(Period, RPIX.YOY, RPIX.INDEX)
})
output$index_table <- renderDataTable({
DT::datatable(df_filt(),
options =
list(dom = "itB",
fixedHeader = T
),
rownames = F
)
})
}
I have similar code to the above that filters based on the row instead, and this works just fine, however, for this column filtering I am getting this error:
Warning in if ({ : the condition has length > 1 and only the first element will be used
I understand that I'm passing a vector to the if statement, but not sure how to recode - would anyone be able to help?

Rshiny : Add a free text for comments

I have a data coming from a server. Now I want to add a free text column ( editable) to add comments to my R shiny application. Once that is done , I want to save it in SQLLite and bring it back once it is refreshed. Please help me with the pointers.
library(shiny)
library(ggplot2) # for the diamonds dataset
ui <- fluidPage(
title = "Examples of DataTables",
sidebarLayout(
sidebarPanel(
conditionalPanel(
'input.dataset === "diamonds"'
)
),
mainPanel(
tabsetPanel(
id = 'dataset',
tabPanel("diamonds", DT::dataTableOutput("mytable1"))
)
)
)
)
library(DT)
server <- function(input, output) {
# choose columns to display
diamonds2 = diamonds[sample(nrow(diamonds), 1000), ]
diamonds2$test <- ifelse(diamonds2$x > diamonds2$y,TRUE,FALSE)
output$mytable1 <- DT::renderDataTable({
DT::datatable(diamonds2[, drop = FALSE],extensions = 'FixedColumns',options = list(
dom = 't',
scrollX = TRUE,
fixedColumns = list(leftColumns =10)
)) %>%
formatStyle(
'x', 'test',
backgroundColor = styleEqual(c(TRUE, FALSE), c('gray', 'yellow'))
)
})
}
Please guide how can I add free text in the end of the table and save it.
Thanks in advance.
Regards,
R
Here is a solution based on DTs editable option. (See this for more information)
Each time the user edits a cell in the "comment" column it is saved to a sqlite database and loaded again after restarting the app:
library(shiny)
library(DT)
library(ggplot2) # diamonds dataset
library(RSQLite)
library(DBI)
# choose columns to display
diamonds2 = diamonds[sample(nrow(diamonds), 1000),]
diamonds2$test <- ifelse(diamonds2$x > diamonds2$y, TRUE, FALSE)
diamonds2$id <- seq_len(nrow(diamonds2))
diamonds2$comment <- NA_character_
con <- dbConnect(RSQLite::SQLite(), "diamonds.db")
if(!"diamonds" %in% dbListTables(con)){
dbWriteTable(con, "diamonds", diamonds2)
}
ui <- fluidPage(title = "Examples of DataTables",
sidebarLayout(sidebarPanel(
conditionalPanel('input.dataset === "diamonds"')
),
mainPanel(tabsetPanel(
id = 'dataset',
tabPanel("diamonds", DT::dataTableOutput("mytable1"))
))))
server <- function(input, output, session) {
# use sqlInterpolate() for production app
# https://shiny.rstudio.com/articles/sql-injections.html
dbDiamonds <- dbGetQuery(con, "SELECT * FROM diamonds;")
output$mytable1 <- DT::renderDataTable({
DT::datatable(
dbDiamonds,
# extensions = 'FixedColumns',
options = list(
dom = 't',
scrollX = TRUE
# , fixedColumns = list(leftColumns = 10)
),
editable = TRUE,
# editable = list(target = "column", disable = list(columns = which(names(diamonds2) %in% setdiff(names(diamonds2), "comment"))))
) %>% formatStyle('x', 'test', backgroundColor = styleEqual(c(TRUE, FALSE), c('gray', 'yellow')))
})
observeEvent(input$mytable1_cell_edit, {
if(input$mytable1_cell_edit$col == which(names(dbDiamonds) == "comment")){
dbExecute(con, sprintf("UPDATE diamonds SET comment = '%s' WHERE id = %s", input$mytable1_cell_edit$value, input$mytable1_cell_edit$row))
}
})
}
shinyApp(ui, server, onStart = function() {
onStop(function() {
dbDisconnect(con) # close connection on app stop
})
})
Initially I wanted to disable editing for all columns except "comment", however, it seems I've found a bug.
The following example adds a <input type="text"> element to each row of the table, where you can add your free text. A simple JavaScript event listener reacts on changes to the text boxes and stores them in the Shiny variable free_text which you can then process on the shiny side according to your needs (in this toy example it is simply output to a verbatimTextOutput).
As for the storing: I would add a save button, which reads input$free_text and saves it back to the data base. To display the text then again in the text boxes is as easy as adding the value in the mutate statement like this mutate(free_text = sprintf("<input type=\"text\" class = \"free-text\" value = \"%s\" />", free_text_field_name))
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
tags$head(
tags$script(
HTML(
"$(function() {
// input event fires for every change, consider maybe a debounce
// or the 'change' event (then it is only triggered if the text box
// loses focus)
$('#tab').on('input', function() {
const inputs = $(this).find('.free-text').map(function() {
return this.value;
})
Shiny.setInputValue('free_text', inputs.get());
})
})
"
)
)
),
fluidRow(
verbatimTextOutput("out")
),
fluidRow(
dataTableOutput("tab")
)
)
server <- function(input, output, session) {
output$tab <- renderDataTable({
my_dat <- mtcars %>%
mutate(free_text =
sprintf("<input type=\"text\" class = \"free-text\" value = \"\" />"))
datatable(my_dat, escape = FALSE,
options = list(dom = "t", pageLength = nrow(mtcars)))
})
output$out <- renderPrint(input$free_text)
}
shinyApp(ui, server)
You may want to have a look at the handsontable package, which allows editing of (columns of) datatable outputs. In your case, you can create a character column and allow editing through the handsontable.
On the topic of persisting data: you table would need either a separate column with comments, or a separate table that maps observations to comment, which is joined. The best solution depends on the volume of comments you expect: if you expect comment to appears sporadically, a separate table may be the best solution. If you expect comments for nearly every row, direct integration into the table may be more favourable. It then becomes a matter of writing to and loading from an SQL database based on user events.

checkboxGroupInput displaying list of table elements rather than table itself

I am creating an R Shiny application primarily using checkboxGroupInput where for each checkbox name I check, the corresponding table should display in the main UI panel. I have linked each checkbox option to its corresponding table (already in my previous script) in the "choices" argument of checkboxGroupInput. I use eventReactive to make a working button and renderTable to produce the appropriate tables. However, what displays in the main panel when I click the button is a list of each cell in the table rather than the table itself. This list of values looks a bit like this:
list(CUI = "C05372341", LAT = "ENG", TS = "P", LUI = "L0883457", STT = "PF", SUI = "S13423408", ISPREF = "N", AUI = "A10344304", SAUI = "21823712", SCUI = "1341953", SDUI = NA, SAB = "LKDHDS", TTY = "IN", CODE = "139433", STR = "Pramlintide", SRL = "0", SUPPRESS = "Y", CVF = "4354")
I would like this to have been printed in table form.
When I simply use renderTable({table_name}) on any given one of the tables, the table prints in the main panel how I would like it to. However, when I use eventReactive, name that variable, and renderTable on that variable, that is when the list of table values prints instead. Any ideas?
library(shiny)
ui <- fluidPage(
titlePanel("RxNorm Diabetic Drug Mapping based on Epocrates Classes"),
sidebarLayout(
sidebarPanel(
checkboxGroupInput("drugs", "Drug Class", choices = list("ALPHA GLUCOSIDASE INHIBITORS" = agi, "AMYLIN MIMETICS" = pramlintide, "BIGUANIDES" = biguanides, "DOPAMINE AGONISTS" = bromocriptine, "DPP4 INHIBITORS" = dpp4, "GLP1 AGONISTS" = glp1, "INSULINS" = insulins, "MEGLITINIDES" = meglitinides, "SGLT2 INHIBITORS" = sglt2, "SULFONYLUREAS" = sulfonylureas, "THIAZOLIDINEDIONES" = thiazolidinediones)),
actionButton("button", "Retrieve Data")
),
mainPanel(
tableOutput("results")
)
)
)
server <- function(input, output) {
table_reactive <- eventReactive(input$button, {
input$drugs
})
output$results <- renderTable({
table_reactive()
})
}
shinyApp(ui = ui, server = server)
In your choices:
choices = list("ALPHA GLUCOSIDASE INHIBITORS" = agi, "AMYLIN MIMETICS" = pramlintide ...), it's not valid if agi and pramlintide ... are pointing to tables. choices values can only be string.
You shouldn't pass variable as values in checkboxGroupInput. Instead you should pass the table name as string.
To answer your questions:
Please see the demos below:
If your tables are saved as separate variables, you should use sym() and eval_tidy() in rlang package to convert string to varaible.
library(shiny)
library(rlang)
ui <- fluidPage(
fluidRow(
checkboxGroupInput(
inputId = "checkgroup",
label = "Select Table",
choices = list(iris = "iris", mtcars = "mtcars")
),
actionButton(
inputId = "confirm",
label = "Confirm Table(s)"
)
),
fluidRow(
tags$div(id = "tables")
)
)
server <- function(input, output, session) {
observeEvent(input$confirm,{
removeUI(selector = "#tables > *",multiple = TRUE)
req(length(input$checkgroup) > 0)
for(table_name in input$checkgroup){
table_id <- paste0("mytable",table_name)
insertUI(
selector = "#tables",
ui = dataTableOutput(table_id)
)
output[[table_id]] <- renderDataTable(eval_tidy(sym(table_name)))
}
})
}
shinyApp(ui, server)

Shiny: Selecting groups using selectizeInput

I have this vision where I have a selector and a user can click the group to select all items in that group. For example, please see this
When you click input box X2 or X4, I would like for the user to be able to click "Western" to select both California and Washington.
Ideally, I would like for the user to be able to select multiple regions, as well as be able to customize their selections (i.e choose "Western" region and look at some data. Then unselect "Washington" to focus on "California" and look at more data.
I'm thinking that if this isn't possible in a simple way, I should just have the regions as choices and use updateSelectInput() to update the selected values, when the user has selected a region.
Thank you for the help.
Afaik using selectizeInput you'll have to rely on a nested/dependent selection of multiple inputs to get something similar to your expected behavior.
Once it’s heading towards hierarchical selection I really like using library(d3Tree) as an alternative approach.
Here is a modified version (adapted to your states link) of one of the d3Tree examples:
library(shiny)
library(d3Tree)
library(DT)
library(data.table)
library(datasets)
DT <- unique(data.table(state.region, state.division, state.name, state.area))
variables <- names(DT)
rootName <- "us.states"
ui <- fluidPage(fluidRow(
column(
7,
column(8, style = "margin-top: 8px;",
selectizeInput(
"Hierarchy",
"Tree Hierarchy",
choices = variables,
multiple = TRUE,
selected = variables,
options = list(plugins = list('drag_drop', 'remove_button'))
)),
column(4, tableOutput("clickView")),
d3treeOutput(
outputId = "d3",
width = '1200px',
height = '475px'
),
column(12, DT::dataTableOutput("filterStatementsOut"))
),
column(5, style = "margin-top: 10px;", DT::dataTableOutput('filteredTableOut'))
))
server <- function(input, output, session) {
network <- reactiveValues(click = data.frame(name = NA, value = NA, depth = NA, id = NA))
observeEvent(input$d3_update, {
network$nodes <- unlist(input$d3_update$.nodesData)
activeNode <- input$d3_update$.activeNode
if (!is.null(activeNode))
network$click <- jsonlite::fromJSON(activeNode)
})
output$clickView <- renderTable({
req({as.data.table(network$click)})
}, caption = 'Last Clicked Node', caption.placement = 'top')
filteredTable <- eventReactive(network$nodes, {
if (is.null(network$nodes)) {
DT
} else{
filterStatements <- tree.filter(network$nodes, DT)
filterStatements$FILTER <- gsub(pattern = rootName, replacement = variables[1], x = filterStatements$FILTER)
network$filterStatements <- filterStatements
DT[eval(parse(text = paste0(network$filterStatements$FILTER, collapse = " | ")))]
}
})
output$d3 <- renderD3tree({
if (is.null(input$Hierarchy)) {
selectedCols <- variables
} else{
selectedCols <- input$Hierarchy
}
d3tree(
data = list(
root = df2tree(struct = DT[, ..selectedCols][, dummy.col := ''], rootname = rootName),
layout = 'collapse'
),
activeReturn = c('name', 'value', 'depth', 'id'),
height = 18
)
})
output$filterStatementsOut <- renderDataTable({
req({network$filterStatements})
}, caption = 'Generated filter statements', server = FALSE)
output$filteredTableOut <- DT::renderDataTable({
# browser()
filteredTable()
}, caption = 'Filtered table', server = FALSE, options = list(pageLength = 20))
}
shinyApp(ui = ui, server = server)
Result:
Edit:
Please also see the more convenient alternative implementation: library(collapsibleTree)

Resources