Reputation: 508
I have a shiny App where users can upload their own data. My goal is to display an interactive table with DT that allows users to control which columns and which rows are displayed. Ultimately, Users should be able to either download all the data that they uploaded (there are some processing steps done in the actual app) or download only the data that they see in their current selection. I thus need to make a copy of the uploaded dataframe instead of editing it in place.
My Problem is that I can make the columns selectable and I can also remove selected rows, but I can't find a way to save the selected rows in between. For example: When users first select rows 1,2 and 3 and click on "Exclude Rows", the rows disappear, but when they then click on rows 4 and 5 and click on "Exclude Rows", row 4 and 5 disappear but 1,2 and 3 pop back up.
Here is what I tried so far:
# Reproducible example
# Define UI
ui <- fluidPage(
navbarPage("Navbar",
tabPanel("Upload Data",
fileInput(inputId = "file", label = "Upload your .csv file",
accept = "text/csv"),
actionButton("submit","Use this dataset")
),
tabPanel("Check Table",
sidebarPanel("Settings",
checkboxGroupInput("show_vars", "Select Columns to display:",
choices = c("type",
"mpg",
"cyl",
"disp",
"hp",
"drat",
"wt",
"qsec",
"vs",
"am",
"gear",
"carb"
),
selected = c("type",
"mpg",
"cyl",
"disp",
"hp",
"drat",
"wt",
"qsec",
"vs",
"am",
"gear",
"carb"
)),
tags$br(),
tags$br(),
actionButton("excludeRows", "Exlcude selected Rows")),
mainPanel(DTOutput("frame"))),
tabPanel("Show Selection",
textOutput("selection"))
)
)
# Define server logic
server <- function(input, output, session) {
# Parsing the uploaded Dataframe according to the right input
data <- eventReactive(input$submit, {read.csv(input$file$datapath)})
# Render the whole dataframe when a new one is uploaded
observeEvent(input$submit, {output$frame <- renderDT(datatable(data()[,c(input$show_vars)]))})
# Making an internal copy for selection purposes
CopyFrame <- eventReactive(data(),{data()})
# excluding selected rows
observeEvent(input$excludeRows,{
if (exists("SelectFrame()")) {
# Updating SelectFrame from SelectFrame
SelectFrame <- eventReactive(input$excludeRows,{SelectFrame()[-c(input$frame_rows_selected),c(input$show_vars)]})
} else {
# creating SelectFrame for the first time from CopyFrame
SelectFrame <- eventReactive(input$excludeRows,{CopyFrame()[-c(input$frame_rows_selected),c(input$show_vars)]})
}
# updating plot
output$frame <- renderDT(datatable(SelectFrame()))
})
# show Selection
output$selection <- renderText(input$frame_rows_selected)
}
# Run the application
shinyApp(ui = ui, server = server)
You can easily create an example file for this reproducible example with:
names(mtcars)[1] <- "type"
write.csv(mtcars, file = "testfile.csv")
Upvotes: 0
Views: 115
Reputation: 10375
Here is a solution that works with the row numbers of the original dataframe:
library(shiny)
library(DT)
# Define UI
ui <- fluidPage(
navbarPage("Navbar",
tabPanel("Upload Data",
fileInput(inputId = "file", label = "Upload your .csv file",
accept = "text/csv"),
actionButton("submit","Use this dataset")
),
tabPanel("Check Table",
sidebarPanel("Settings",
checkboxGroupInput("show_vars", "Select Columns to display:",
choices = c("type",
"cyl",
"disp",
"hp",
"drat",
"wt",
"qsec",
"vs",
"am",
"gear",
"carb"
),
selected = c("type",
"cyl",
"disp",
"hp",
"drat",
"wt",
"qsec",
"vs",
"am",
"gear",
"carb"
)),
tags$br(),
tags$br(),
actionButton("excludeRows", "Exlcude selected Rows")),
mainPanel(DTOutput("frame"))),
tabPanel("Show Selection",
textOutput("selection"))
)
)
# Define server logic
server <- function(input, output, session) {
# initialise index which rows are shown
rows_shown <- reactiveVal()
# Parsing the uploaded Dataframe according to the right input
data <- eventReactive(input$submit, {
data <- read.csv(input$file$datapath)
data <- cbind(data, data.frame(row_number = seq_len(nrow(data))))
data
})
# Making an internal copy for selection purposes
CopyFrame <- eventReactive(input$submit, {data()})
observeEvent(input$submit, {
# set up row index
rows_shown(seq_len(nrow(data())))
})
# excluding selected rows
observeEvent(input$excludeRows,{
# use an extra column for the row numbers to refer to the row number of the
# original dataframe and not the subsetted one
actual_row_numbers <- CopyFrame()[rows_shown(), "row_number"][input$frame_rows_selected]
row_index <- !rows_shown() %in% actual_row_numbers
new_rows <- rows_shown()[row_index]
rows_shown(new_rows)
})
# show Selection
output$selection <- renderText(input$frame_rows_selected)
# show dataframe
output$frame <- renderDT({
datatable(CopyFrame()[rows_shown(), input$show_vars])
})
}
# Run the application
shinyApp(ui = ui, server = server)
Upvotes: 1
Reputation: 30559
Perhaps you could use reactiveValues
to store your edited data frame. When you load a new csv file, store in rv$data
. Then, when you exclude rows, you can modify your data frame each time and replace rv$data
with the result. Your output$frame
can just show this modified rv$data
and only the columns selected via input$show_vars
. Would this work for you?
server <- function(input, output, session) {
rv <- reactiveValues(data = NULL)
observeEvent(input$submit, {
rv$data <- read.csv(input$file$datapath)
})
observeEvent(input$excludeRows,{
rv$data <- rv$data[-c(input$frame_rows_selected),c(input$show_vars)]
})
output$frame <- renderDT({
datatable(rv$data[c(input$show_vars)])
})
}
Upvotes: 2