I am attempting to use DT in R to create a table for user review with an appended column (new_column) including a selectInput drop-down that allows them to designate the event as either keep or delete. I am stuck on how to retrieve both the unique id and the user input from the datatable to be able to apply the change to a separate dataframe in Shiny. I would like for the user to push a button ('submit') for the app to know when to capture the values -- with this am I able to circumvent rerending the table or is that a necessary part of capturing the value?
I have worked with Shiny quite a bit but am not super familiar with javascript or datatables in general so have been stuck on this for some time and would appreciate any pointers.
Here is a simplified version of my data and code:
library(DT)
library(tidyverse)
library(shiny)
ui <- fluidPage(
DTOutput('myTableOutput'),
br(),
actionButton("submit", "Apply Changes"))
server <- function(input, output, session) {
for (i in 1:nrow(df)) {
df$new_column[i] <- as.character(selectInput(inputId = df$unique_id[i], label=NULL, choices = c('keep'=TRUE, 'delete'=FALSE)))
}
output$myTableOutput <- DT::renderDataTable({
datatable(
df,
escape = FALSE,
filter = "none",
editable = 'new_column',
selection = "none",
options = list(
dom = "t",
initComplete = JS(
"function(settings, json) {",
"$(this.api().table().header()).css({'background-color': 'DimGray', 'color': 'white'});",
"}"),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } '))
)
}, server = FALSE)
observeEvent(input$submit,{
#need to retrieve changes (preferably in a df) so I can apply them to another dataframe
reviewed_table <- as.data.frame(input$mytable_cell_edit)
})
}
You can add reactive value (lgl_vec) that has values of all selectInput's inside your table:
server <- function(input, output, session) {
rv <- reactiveValues()
df[["new_column"]] <- map_chr(
map(
df[["unique_id"]],
selectInput,
label = NULL,
choices = c(keep = TRUE, delete = FALSE)
),
as.character
)
output$myTableOutput <- DT::renderDataTable({
datatable(
df,
escape = FALSE,
filter = "none",
selection = "none",
options = list(
dom = "t",
preDrawCallback =JS(
'function(){Shiny.unbindAll(this.api().table().node());}'
),
drawCallback = JS(
'function(){Shiny.bindAll(this.api().table().node());}'
)
)
)
}
)
observeEvent(input$submit, {
lgl_vec <- as.logical(map_chr(df[["unique_id"]], ~input[[.x]]))
rv$reviewed_table <- df[lgl_vec, -ncol(df), drop = FALSE]
print(rv$reviewed_table)
})
}
Related
I am quite new to R shiny and I am trying to build a small shiny app but I don't know where I went wrong.
I am trying to get multiple user input via text area to filter my table output. Moreover, i want to control the columns to show in the table as well. Code is running fine for showing the columns but it is working only with one input value in the text area, it is not working with multiple user inputs.
I want to filter the table output with multiple user inputs as well.
For example for this code snippet it should return table when I write "honda,audi,bmw" in the text area input.
library(shiny)
library(shinyWidgets)
library(DT)
df <-mtcars
#ui
shinyApp(
ui = fluidPage(
titlePanel("Trial 1"),
sidebarLayout(
sidebarPanel(
#to take multiple user input
textAreaInput(
"text_input",
label = "Write multiple input separated by comma"
),
#to slect the columns to be added
pickerInput(
inputId = "somevalue",
label = "Columns to add",
choices = colnames(df),
options = list(`actions-box` = TRUE),
multiple = TRUE
),
#action button tot show the table
actionBttn(
"show_table",
label = "Show",
size = "sm",
color = "default",
block = TRUE
),
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Table", DT::dataTableOutput("table")),
tabPanel("Summary", verbatimTextOutput("summary"))
)
)
)
),
server = function(input, output,session) {
data <- observeEvent(input$show_table,{
text_input <- trimws(strsplit(input$text_input, ",")[[1]])
output$summary <- renderPrint({
summary(data())
})
output$table <- DT::renderDT({
df_sub <- df[df$make %chin% input$text_input, input$somevalue]
#df_sub = df[ ,input$somevalue]
datatable(df_sub,
caption = "PLease enter the changes by double clicking the cell",
editable = 'cell')
})
})
}
)
There isn't a 'make' variable in the data. I guess you refer to the first word of the row name as the make of the car. Then the strings you entered could be matched with the make of the car.
server = function(input, output,session) {
data <- observeEvent(input$show_table,{
brand <- word(rownames(df), 1)
text_input <- strsplit(input$text_input, ",")[[1]]
df_sub <- df[brand %in% text_input, input$somevalue]
output$summary <- renderPrint({
summary(df_sub)
})
output$table <- DT::renderDT({
datatable(df_sub,
caption = "PLease enter the changes by double clicking the cell",
editable = 'cell')
})
output$test <- renderText({
text_input
})
})}
I want to have the select capability only on the second column using DT Package in shiny apps.
In this post this question was asked
Using the above solution in DT package in shiny is not working for me. I expect by the code below that clicking on the second column, only, row will be selected.
Any hints on how I use the selector correctly?
data <- data.frame(
a = 1:10,
b = letters[1:10]
)
ui <- fluidPage(
DT::DTOutput("table")
)
server <- function(input, output, session) {
output$table <- DT::renderDT({
DT::datatable(
data = data,
options = list(
select = list(
style = "os",
selector = 'tr>td:nth-child(2)'
)
)
)
}, server = F)
}
shinyApp(ui, server)
The selector is alright, the functionalities to enable a "better" selection are contained in the Select extension. As documented in the extensions page, you need:
Enable the Select extension
Turn off DT’s own select functionality:
data <- data.frame(
a = 1:10,
b = letters[1:10]
)
ui <- fluidPage(
DT::DTOutput("table")
)
server <- function(input, output, session) {
output$table <- DT::renderDT({
DT::datatable(
data = data,
options = list(
select = list(
style = "os",
selector = 'tr>td:nth-child(2)'
)
),
extensions = c("Select"),
selection = 'none'
)
}, server = F)
}
shinyApp(ui, server)
I have been trying to merge data with another data set based on input from a drop down. I have just started learning R and have run into some problems and want to know if there is a better way of going about this.
I am getting an error that it cannot coerce class c(ReactiveExpr, reactive) to a data frame.
library(shiny)
library(plyr)
library(dplyr)
library(xlsx)
server <- function(input, output){
annotation1 <- read.xlsx("input1.xlsx", sheetIndex = 1, header = TRUE)
annotation2 <- read.xlsx("input2.xlsx", sheetIndex = 1, header = TRUE)
data_input <- eventReactive(input$userfile, {
df <- read.xlsx(input$userfile$datapath, sheetIndex = 1, header = TRUE)
})
output$data_input <- renderTable(data_input())
output$annotation <- renderTable(annotation)
data_species <- c("Set1", "Set2")
# Drop-down selection box for which data set
output$choose_species <- renderUI ({
selectInput("species", "Species", as.list(data_species))
})
output$mergeddata <- renderTable({
if(input$species == "Set1"){
eventReactive("Set1",({left_join(data_input(), annotation1, by = c("Column1" = "Column1"))}))
}
else if(input$species == "Set2"){
eventReactive("Set2",({left_join(data_input(), annotation2, by = c("Column1" = "Column1"))}))
}
})
}
ui <- fluidPage(
titlePanel(
div("Test")
),
sidebarLayout(
sidebarPanel(
fileInput("userfile", "Input File", multiple =FALSE,
buttonLabel = "Browse Files", placeholder = "Select File"),
uiOutput("choose_species"),
uiOutput("choose_annotations"),
),
mainPanel(
tableOutput("mergeddata"),
br()
),
),
)
# Run the application
shinyApp(ui = ui, server = server)
In general, you approach seems ok. The error you get is from the line
eventReactive("Set1",({left_join(data_input(), annotation1, by = c("Column1" = "Column1"))}))
An eventReactive returns an (unevaluated) reactive expression which you try to render as data.frame with renderTable. To circumvent this, you could use:
eventReactive("Set1",({left_join(data_input(), annotation1, by = c("Column1" = "Column1"))}))()
However, here you don't need eventReactive, because your reactivity comes from input$species (you want to change the table output based on this input). Therefore, you can just use:
output$mergeddata <- renderTable({
if(input$species == "Set1"){
merge_data <- annotation1
} else {
merge_data <- annotation2
}
left_join(data_input(), merge_data, by = c("Column1"))
})
I was wondering if it is possible to save DT table content together with some additional information which is not part of the data frame/table like app version number, date of execution, sliderInput value etc.
Thank you!
Reprex below:
library(shiny)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "range", "Set range", 1, 10, 5, 1)
),
mainPanel(
DT::dataTableOutput("table")
)
)
)
server <- function(input, output) {
dfr <- data.frame(var1 <- c(1,2,3),
var2 <- c(11, 22, 33))
output$table <- DT::renderDataTable(
datatable(dfr, extensions = 'Buttons',
class="cell-border stripe",
rownames = FALSE, colnames = c("var1", "var2"),
options = list(dom = "Blfrtip",
buttond = list("copy", list(extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")), pageLength=10, autoWidth = TRUE,
searchHighlight = TRUE, filter = "top"))
)
}
shinyApp(ui = ui, server = server)
You could save the contents of the data frame and the other information in a list and then save the list.
Or, any R object can have attributes which are completely arbitrary and under your control. You could set attributes of the data frame to record the information you want.
Personally, I'd use the list approach, purely because I don't like attributes.
Here's a suggestion in response to OP's request below.
library(shiny)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "range", "Set range", 1, 10, 5, 1),
actionButton("saveRds", "Save to Rds"),
actionButton("loadRds", "Load from Rds")
),
mainPanel(
DT::dataTableOutput("table"),
wellPanel(h4("Current data"), verbatimTextOutput("text")),
wellPanel(h4("File data"), verbatimTextOutput("loadedData"))
)
)
)
server <- function(input, output) {
dfr <- data.frame(var1 <- c(1,2,3),
var2 <- c(11, 22, 33))
output$table <- DT::renderDataTable(
datatable(dfr, extensions = 'Buttons',
class="cell-border stripe",
rownames = FALSE, colnames = c("var1", "var2"),
options = list(dom = "Blfrtip",
buttond = list("copy", list(extend = "collection",
buttons = c("csv", "excel", "pdf"),
text = "Download")), pageLength=10, autoWidth = TRUE,
searchHighlight = TRUE, filter = "top"))
)
listInfo <- reactive({
list("data"=dfr, "version"="WebApp Version 1.0", "runDate"=date(), "sliderValue"=input$range)
})
output$text <- renderPrint({
listInfo()
})
observeEvent(input$saveRds, {
saveRDS(listInfo(), "data.Rds")
})
fileData <- reactive({
req(input$loadRds)
readRDS("data.Rds")
})
output$loadedData <- renderPrint({
fileData()
})
}
shinyApp(ui = ui, server = server)
The way you implement "save to file" will depend on the file format: Excel files will clearly have different requirements to PDF files, for example. As a minimum effort demonstation, I've created "Save to Rds" and "Load from RDS" buttons in the sidebar and added a verbatimTextOutput to display the contents of the file when it's loaded. [I'm not sufficiently familiar with DT to know how to add the buttons in the table toolbar.]
OP's effort was pretty close: it's just that writing a list to CSV file takes a little more effort than just calling write.csv...
I want to upload a .csv file. Then, update the radio button choices as column names of the uploaded file and then, through that radio button choose which columns to show. The problem is whenever I run the code, it gives me this error.
P.S.1. Is there any way to read the data before we run this app? like in another app?
library(shiny)
ui = basicPage(
fileInput('uploadedcsv', "", accept = '.csv'),
radioButtons(
"column1",
"select columns",
choices = "",
inline = T
),
radioButtons(
"column2",
"select columns",
choices = "",
inline = T
),
dataTableOutput('mytable')
)
server = function(session,input, output) {
z <- reactive({
infile <- input$uploadedcsv
if (is.null(infile))
return(NULL)
read.csv(infile$datapath, header = TRUE, sep = ",")
})
observe({
vchoices <- names(z())
updateRadioButtons(session, "column1", choices = vchoices)
updateRadioButtons(session, "column2", choices = vchoices)
})
z <- reactive(z[,c(input$column1,input$column2)])
output$mytable = renderDataTable(z())
}
shinyApp(ui = ui, server = server)
z is the closure that is not sub-settable:
z <- reactive(z[,c(input$column1,input$column2)])
z is a reactive function returned by your first assignment. It is not subsettable (you cannot index it) because it is a function. You can call z and index the result as in renderDataTable below. renderDataTable will call z() and is reactive to changes in z's output, input$column1 and input$column2.
server = function(input, output, session) {
# z is reactive to a change in the input data
z <- reactive({
infile <- input$uploadedcsv
if (is.null(infile))
return(NULL)
read.csv(infile$datapath, header = TRUE, sep = ",")
})
observe({
vchoices <- names(z())
updateRadioButtons(session, "column1", choices = vchoices)
updateRadioButtons(session, "column2", choices = vchoices)
})
# renderDataTable is reactive to a change in the input data
# or the selected columns
output$mytable = renderDataTable({
z()[,c(input$column1, input$column2)]
})
}