Having isolated or non-reactive columns inside of a reactive dataframe - r

I have a reactive dataframe (called selected_df()) in which I am trying to consolidate information from input$tableID_cells_selected and another dataframe.
The second reactive dataframe, called storage_df(), is 1 row and 2 columns. It collects the background color and text labels from action buttons that are pressed, and then that data stays static in storage_df() until a different button is pressed.
selected_df() then collects the info about whatever button was last pressed from storage_df() when a cell in the table is selected (or whenever input$plate_cells_selected is updated), and shows these data in the same row.
The problem is that the selected_df() must reference storage_df() inside of the reactive environment, so it updates all of the values in the cond_selected and color_selected columns from selected_df(). I don't want to have those old values from storage_df() updated and replaced with whatever new values exist in storage_df(). I want those old rows of selected_df() to keep those old values, and for the new rows of selected_df() to have the new values of storage_df(). So basically, storage)df() is updated upon button click, as it is reactive, but the references to storage_df() made by selected_df() would not be reactive.
I have a gif here that will hopefully explain what I am trying to do, in case this is confusing. This is a previous attempt but it is the closest that I have gotten to success. In the gif, the color and cond columns of selected_df() are set such as follows for example: cond_selected = isolate(paste0(rep(storage_df()[[1,2]]))), so that value changes when a new button is pressed. The first three rows of column cond_selected within selected_df() should ideally stay gse1, while the latter three rows should be cox8a. As you can see, this is not what happens.
In other words, this is what I have in these columns at the end of this gif:
And this is what I want in these columns:
With my latest attempt (included in my MRE). I thought if I were to only try to update the values of the last added row, as I think the selected_df() might be appended whenever a new cell is selected, then that might work. However, the app crashes and only gives this warning, which it usually gave because selected_df() had no rows before a cell was selected:
Warning: Error in [: subscript out of bounds
Also, here is my MRE and latest attempt, and what I currently need help trying to figure out:
library(shiny)
library(colourpicker)
library(dplyr)
library(DT)
library(glue)
library(shinyWidgets)
####Create the matrix and organization for the 96 well plate####
plate96 <- function(id) {
div(
style = "position: relative; height: 500px",
tags$style(HTML('
.wells {
transform: translateX(50%);
}
.wells table.dataTable tr:nth-child(9) td { /*for the row 9, need to make it not look like a row*/
border-bottom: unset;
}
.wells tbody tr td:not(:first-of-type) {
border: 1px solid black;
height: 15px;
width: 15px;
padding: 15px;
font-size: 0;
}
')),
div(
style = "position: absolute; left: 50%; transform: translateX(-100%);",
div(
class = "wells",
DTOutput(id, width = "90%", height= "100%")
)
)
)
}
####Create the matrix and organization for the 96 well plate####
renderPlate96 = function(id, colors = rep("white", 108)) {
plate <- matrix(1:108,
nrow = 9,
ncol = 12,
byrow = TRUE,
dimnames = list(LETTERS[1:9], 1:12))
colnames (plate) = stringr::str_pad(colnames(plate), 2, "left", "0")
return(plate_return1 <-
datatable(
plate,
options = list(dom = 't', ordering = F),
selection = list(mode = 'multiple',
target = "cell"),
class = 'cell-border compact'
) %>%
formatStyle(
1:12,
cursor = 'pointer',
backgroundColor = styleEqual(1:108, colors, default = NULL)
)
)
}
storage_df <- (data.frame(
matrix(ncol = 2, nrow = 1),
color_selected = NA,
cond_selected = NA
))
# app code
ui <- fluidPage(
plate96("plate"),
tags$b("Wells Selected:"),
verbatimTextOutput("plateWells_selected"),
br(),
helpText("Step 1: Add in a couple of buttons"),
numericInput("num_conds",
label = h3("Enter the number of treatments/ conditions"),
min = 1,
max = 20,
value = 1),
htmlOutput("cond_buttons", align = 'center'),
helpText("Step 2: Type in any name for a condition for the buttons"),
uiOutput("boxes_conds"),
helpText("Step 3: Choose any color for the buttons"),
uiOutput("cond_colors"),
helpText("Step 4: Select cells from the table above"),
DT::dataTableOutput("selected_table"),
DT::dataTableOutput("storage_table"),
)
server <- function(input, output, session){
### ★★ ↓↓↓↓ PROBLEM AREA ↓↓↓↓ ★★ ###
####Storage data.frame for when the buttons are clicked####
observeEvent(input$num_conds, {
lapply(1:input$num_conds, function(x){
observeEvent(input[[paste0("cond_buttons",x)]], {
newdf <- tibble(
color_selected = input[[paste0("colors",x)]],
cond_selected = input[[paste0("condID",x)]]
)
storage_df(newdf)
})
})
})
storage_df <- reactiveVal(tibble::tribble(
~color_selected, ~cond_selected
))
output$storage_table <- renderDataTable(
req(storage_df()),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
####Create a DT that stores the values of the cells selected in the plate####
selected_df <- reactive(data.frame(rows = req(input$plate_cells_selected[,1]),
columns = req(input$plate_cells_selected[,2]),
color_selected = 0,
cond_selected = 0,
stringsAsFactors = FALSE),
)
####Take out this portion of the code if trying to reproduce my GIF###
observeEvent(input$plate_cells_selected, {
selected_df() %>% mutate(selected_df(), color_selected = replace(color_selected, color_selected== '0', isolate(paste0(rep(storage_df()[[1,1]]))))
)})
####Take out this portion of the code if trying to reproduce my GIF###
output$selected_table <- renderDataTable(
req(selected_df()),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
lengthChange = FALSE
)
)
### ★★ ↑↑↑↑ PROBLEM AREA ↑↑↑↑ ★★ ###
#....#
#Past here isn't as important to the question...#
####Input for user browse and data upload####
output$contents <- renderTable({ req(input$data) })
#####Slider for frames per second####
output$value <- renderPrint({ input$Frames })
#####Check boxes for no-movement cell exclusion####
output$value <- renderPrint({ input$emptyWell_checkbox })
#####Number output for number of conditions#####
output$value <- renderPrint({ input$num_conds })
#### Condition boxes for UI text input####
output$boxes_conds <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
cond_names <- textInput(paste0("condID", i),
label = paste0("Treatment/ Conditions: ", i),
placeholder = "Enter condition..."
)
})
})
#### Color selection for UI input####
output$cond_colors <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
colourInput(paste0("colors", i),
label = (paste0("Select a color for condition ", i)),
show = c("both"),
value = "black",
palette = c("limited"),
)
})
})
#### Create action buttons for conditions to be selected####
output$cond_buttons <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
bg = input[[paste0("colors", i)]]
style = paste0(
collapse = " ",
glue("background-color:{bg};
color:#ffffff;
border-color:#000000")
)
label = input[[paste0("condID", i)]]
actionButton(paste0("cond_buttons", i),
label = label,
style = style,
)
})
})
####Create the 96 well plate image####
output$plate <- renderDT({
renderPlate96()
})
output$plateWells_selected <- renderPrint({
input$plate_cells_selected
})
}
shinyApp(ui = ui, server = server)
UPDATE UPON REQUEST
Here is a portion of the code needed to reproduce what you see in the GIF. This is not my latest attempt and is not what I need help with troubleshooting, this is merely to give what I used to make the GIF that explains what I want. Just replace the similar version of the code with this in my MRE and take out the mutate function. The mutate function to remove has been marked by comments in my MRE.
####Create a DT that stores the values of the cells selected in the plate####
selected_df <- reactive(data.frame(rows = req(input$plate_cells_selected[,1]),
columns = req(input$plate_cells_selected[,2]),
color_selected = isolate(paste0(rep(storage_df()[[1,1]]))),
cond_selected = isolate(paste0(rep(storage_df()[[1,2]]))),
stringsAsFactors = FALSE)
)
UPDATE, REPONSE TO YBS

Try this
# app code
ui <- fluidPage(
plate96("plate"),
tags$b("Wells Selected:"),
verbatimTextOutput("plateWells_selected"),
br(),
helpText("Step 1: Add in a couple of buttons"),
numericInput("num_conds",
label = h3("Enter the number of treatments/ conditions"),
min = 1,
max = 20,
value = 1),
htmlOutput("cond_buttons", align = 'center'),
helpText("Step 2: Type in any name for a condition for the buttons"),
uiOutput("boxes_conds"),
helpText("Step 3: Choose any color for the buttons"),
uiOutput("cond_colors"),
helpText("Step 4: Select cells from the table above"),
DTOutput("selected_table"),
DTOutput("storage_table"),
)
server <- function(input, output, session){
### ★★ ↓↓↓↓ PROBLEM AREA ↓↓↓↓ ★★ ###
# storage_df <- reactiveVal(tibble::tribble(
# ~color_selected, ~cond_selected
# ))
storage_df <- reactiveVal(storage)
####Storage data.frame for when the buttons are clicked####
observeEvent(input$num_conds, {
lapply(1:input$num_conds, function(x){
observeEvent(input[[paste0("cond_buttons",x)]], {
newdf <- data.frame(
color_selected = input[[paste0("colors",x)]],
cond_selected = input[[paste0("condID",x)]]
)
storage_df(newdf)
}, ignoreInit = TRUE)
})
})
output$storage_table <- renderDataTable(
req(storage_df()),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
selected <- reactiveValues(df=NULL,cum=NULL)
df1 <- data.frame()
observeEvent(input$plate_cells_selected, {
n = dim(req(input$plate_cells_selected))[1]
df1 <<- data.frame(rows = req(input$plate_cells_selected[,1]), columns = req(input$plate_cells_selected[,2]))
###Create a DT that stores the values of the cells selected in the plate####
selected$cum <- rbind(selected$df,data.frame(rows = input$plate_cells_selected[n,1],
columns = input$plate_cells_selected[n,2],
color_selected = storage_df()[1,1], cond_selected = storage_df()[1,2]))
}, ignoreNULL=FALSE)
observeEvent(selected$cum, {
n1 = dim(df1)[1]
n2 = dim(selected$cum)[1]
if (n1 > n2) { ## add a row
df <- selected$cum
}else df <- left_join(df1, selected$cum, by=c("rows","columns"))
selected$df <- df[!duplicated(df[,1:2]),]
#print(selected$df)
})
####Create a DT that stores the values of the cells selected in the plate####
# selected_df <- reactive(data.frame(rows = req(input$plate_cells_selected[,1]),
# columns = req(input$plate_cells_selected[,2]),
# stringsAsFactors = FALSE) %>% mutate(color_selected = c(0), cond_selected = c(0))
# )
output$selected_table <- renderDT(
#selected_df(),
selected$df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
lengthChange = FALSE
)
)
### ★★ ↑↑↑↑ PROBLEM AREA ↑↑↑↑ ★★ ###
#....#
#Past here isn't as important to the question...#
####Input for user browse and data upload####
output$contents <- renderTable({ req(input$data) })
#####Slider for frames per second####
output$value <- renderPrint({ input$Frames })
#####Check boxes for no-movement cell exclusion####
output$value <- renderPrint({ input$emptyWell_checkbox })
#####Number output for number of conditions#####
output$value <- renderPrint({ input$num_conds })
#### Condition boxes for UI text input####
output$boxes_conds <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
cond_names <- textInput(paste0("condID", i),
label = paste0("Treatment/ Conditions: ", i),
placeholder = "Enter condition..."
)
})
})
#### Color selection for UI input####
output$cond_colors <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
colourInput(paste0("colors", i),
label = (paste0("Select a color for condition ", i)),
show = c("both"),
value = "black",
palette = c("limited"),
)
})
})
#### Create action buttons for conditions to be selected####
output$cond_buttons <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
bg = input[[paste0("colors", i)]]
style = paste0(
collapse = " ",
glue("background-color:{bg};
color:#ffffff;
border-color:#000000")
)
label = input[[paste0("condID", i)]]
actionButton(paste0("cond_buttons", i),
label = label,
style = style,
)
})
})
####Create the 96 well plate image####
output$plate <- renderDT({
renderPlate96()
})
output$plateWells_selected <- renderPrint({
input$plate_cells_selected
})
}
shinyApp(ui = ui, server = server)

Related

Why is reactivity with rhandsontable not working when crossing tab panels?

I'm running two expandable rhandsontables who should always have the same number of columns and the same column headers, though the rows differ. One of the tables (myDF1 rendered in "hottable1") is the master where the user adds/deletes columns from the tabPanel() housing that table and the second table (myDF2 rendered in "hottable2") parrots the first table in terms of number of columns and column headers but is placed in a separate tabPanel() reacting to the action buttons in the first tabPanel(). The strange thing is, this linked column addition/deletion works fine when the two tables are rendered in Shiny's fluidPage() or when using Shiny's pageWithSidebar() the two tables are housed in the same tabPanel(). However, when the two tables are in separate tabPanels() (as shown in the code below), column addition works fine but the second table in tab "Slave" crashes when deleting columns from tab "Master".
I must be missing something very basic about tabPanels(). What am I doing wrong?
I've always assumed reactivity cuts across tabPanels().
Code:
library(dplyr)
library(rhandsontable)
library(shiny)
myDF1 <- data.frame('Series 1' = c(1,24,0), check.names = FALSE)
rownames(myDF1) <- c('Term A','Term B','Term C')
myDF2 <- data.frame('Series 1' = c(20,15), check.names = FALSE)
rownames(myDF2) <- c('Boy','Girl')
ui <- pageWithSidebar(
headerPanel(""),sidebarPanel(""),
mainPanel(
tabsetPanel(
tabPanel("Master table", hr(),
rHandsontableOutput('hottable1'),br(),
actionButton("addSeries", "Add", width = 80),
fluidRow(
column(2,actionButton("delSeries","Delete", width = 80)),
column(3,uiOutput("delSeries2"))
),
),
tabPanel("Slave table", hr(),rHandsontableOutput('hottable2'))
)
)
)
server <- function(input, output) {
emptyTbl1 <- reactiveVal(myDF1)
emptyTbl2 <- reactiveVal(myDF2)
observeEvent(input$hottable1, {emptyTbl1(hot_to_r(input$hottable1))})
observeEvent(input$hottable2, {emptyTbl2(hot_to_r(input$hottable2))})
output$hottable1 <- renderRHandsontable({
rhandsontable(emptyTbl1(),rowHeaderWidth = 100, useTypes = TRUE)%>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE)
})
output$hottable2 <- renderRHandsontable({
rhandsontable(emptyTbl2(),rowHeaderWidth = 100, useTypes = TRUE)%>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE)
})
observeEvent(input$addSeries, {
newCol1 <- data.frame(c(1,24,0))
newCol2 <- data.frame(c(20,15))
names(newCol1) <- paste("Series", ncol(hot_to_r(input$hottable1)) + 1)
names(newCol2) <- paste("Series", ncol(hot_to_r(input$hottable2)) + 1)
emptyTbl1(cbind(emptyTbl1(), newCol1))
emptyTbl2(cbind(emptyTbl2(), newCol2))
})
observeEvent(input$delSeries3, {
tmp1 <- emptyTbl1()
tmp2 <- emptyTbl2()
if(ncol(tmp1) > 1){
delCol <- input$delSeries3
tmp1 <- tmp1[ , !(names(tmp1) %in% delCol), drop = FALSE]
tmp2 <- tmp2[ , !(names(tmp2) %in% delCol), drop = FALSE]
newNames <- sprintf("Series %d",seq(1:ncol(tmp1)))
names(tmp1) <- newNames
names(tmp2) <- newNames
emptyTbl1(tmp1)
emptyTbl2(tmp2)
}
})
output$delSeries2 <-
renderUI(
selectInput("delSeries3",
label = NULL,
choices = colnames(hot_to_r(input$hottable1)),
selected = "", width = '100px',
multiple = TRUE)
)
}
shinyApp(ui,server)
The below "resolved code" resolves the issue. The few changes from OP code are commented below and are summarized as follows:
Insert outputOptions(output, 'hottable2', suspendWhenHidden = FALSE) in the server() section in order to update the 2nd table located in a separate tab panel from the action buttons driving that table from another tab panel; allows reactivity to instantly cross tab panels that aren't being viewed
Even with the above fix, the "hottable2" table had to be clicked on in order to completely render it. R whiz Stéphane Laurent pointed out that there's a known bug in Shiny when re-rendering this way, his html solution is accordingly included and commented in the revised code below for the rhandsontable() function used for "hottable2" in the server() section
Resolved code:
library(dplyr)
library(rhandsontable)
library(shiny)
myDF1 <- data.frame('Series 1' = c(1,24,0), check.names = FALSE)
rownames(myDF1) <- c('Term A','Term B','Term C')
myDF2 <- data.frame('Series 1' = c(20,15), check.names = FALSE)
rownames(myDF2) <- c('Boy','Girl')
ui <- pageWithSidebar(
headerPanel(""),sidebarPanel(""),
mainPanel(
tabsetPanel(
tabPanel("Master table", hr(),
rHandsontableOutput('hottable1'),br(),
actionButton("addSeries", "Add", width = 80),
fluidRow(
column(2,actionButton("delSeries","Delete", width = 80)),
column(3,uiOutput("delSeries2"))
),
),
tabPanel("Slave table", hr(),rHandsontableOutput('hottable2'))
)
)
)
server <- function(input, output) {
emptyTbl1 <- reactiveVal(myDF1)
emptyTbl2 <- reactiveVal(myDF2)
observeEvent(input$hottable1, {emptyTbl1(hot_to_r(input$hottable1))})
observeEvent(input$hottable2, {emptyTbl2(hot_to_r(input$hottable2))})
output$hottable1 <- renderRHandsontable({
rhandsontable(emptyTbl1(),rowHeaderWidth = 100, useTypes = TRUE)%>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE) %>%
hot_cols(colWidths = 80)
})
output$hottable2 <- renderRHandsontable({
rhandsontable(emptyTbl2(),rowHeaderWidth = 100, width = 800, height = 450,useTypes = TRUE)%>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE) %>%
hot_cols(colWidths = 80) %>%
# next section of html addresses issue of correcltly rendering the slave table:
htmlwidgets::onRender(
"function(el, x){
var hot = this.hot;
$('a[data-value=\"Slave table\"').on('click', function(){
setTimeout(function(){ hot.render(); }, 200);
});
}"
)
})
observeEvent(input$addSeries, {
newCol1 <- data.frame(c(1,24,0))
newCol2 <- data.frame(c(20,15))
names(newCol1) <- paste("Series", ncol(hot_to_r(input$hottable1)) + 1)
names(newCol2) <- paste("Series", ncol(hot_to_r(input$hottable2)) + 1)
emptyTbl1(cbind(emptyTbl1(), newCol1))
emptyTbl2(cbind(emptyTbl2(), newCol2))
})
observeEvent(input$delSeries3, {
tmp1 <- emptyTbl1()
tmp2 <- emptyTbl2()
if(ncol(tmp1) > 1){
delCol <- input$delSeries3
tmp1 <- tmp1[ , !(names(tmp1) %in% delCol), drop = FALSE]
tmp2 <- tmp2[ , !(names(tmp2) %in% delCol), drop = FALSE]
newNames <- sprintf("Series %d",seq(1:ncol(tmp1)))
names(tmp1) <- newNames
names(tmp2) <- newNames
emptyTbl1(tmp1)
emptyTbl2(tmp2)
}
})
output$delSeries2 <-
renderUI(
selectInput("delSeries3",
label = NULL,
choices = colnames(hot_to_r(input$hottable1)),
selected = "", width = '100px',
multiple = TRUE)
)
outputOptions(output, 'hottable2', suspendWhenHidden = FALSE) # this updates slave panel even when hidden
}
shinyApp(ui,server)

Shiny app: Using indices listed in one dataframe for use in a replace function targeted at another dataframe

This is in reference to the code written by YBS in another question. If YBS happens to see this and is interested/has the time to answer, I would love to hear your input :D
Having isolated or non-reactive columns inside of a reactive dataframe
I have a dataframe called storage$df that stores the indices of selected cells of a datatable. It also stores the values of a reactive dataframe called storage_df(), and keeps those values constant after they are added to storage$df (thanks again to YBS).
I am trying to use the values in selected$df to replace data in a couple of dataframes (colors_df and conds_df) that match the dimensions of the original datatable (plate), but include the values from selected$df as further explained below:
I have been looking at trying to use mapply to make this work, but I haven't figured this out yet. As of now, I have a code that uses a system of for loops (which I know isn't ideal for dataframes, but I figured I may as well try the easy base option first) that works well when the values in selected$df are completely static and the loops are outside of an observeEvent. However, as soon as I implant the loops into an observeEvent, colors_df and conds_df are no
longer updated. I do not get an error message, however. I'm not sure if this is because I'm trying to use for loops inside of observeEvent or if I'm trying to index selected$df incorrectly.
Obviously, I would love to use whatever preferred methods there are. However, because of how I'm trying to use the indices from selected$df to replace data in my colors_df and conds_df tables, this has proven difficult to use apply variants (or at least I haven't figured it out yet).
Here is my reproducible example, sorry that it's a little long. I would normally cut it down more by taking out some of the reactives but I'm wondering they are part of the reason why this isn't working.
NOTE: There are instructional steps listed as helpText in the app to make it work.
library(shiny)
library(dplyr)
library(DT)
library(glue)
library(shinyWidgets)
library(colourpicker)
library(shinyjs)
####Create the matrix and organization for the 96 well plate####
plate96 <- function(id) {
div(
style = "position: relative; height: 500px",
tags$style(HTML('
.wells {
transform: translateX(50%);
}
.wells table.dataTable tr:nth-child(9) td { /*for the row 9, need to make it not look like a row*/
border-bottom: unset;
}
.wells tbody tr td:not(:first-of-type) {
border: 1px solid black;
height: 15px;
width: 15px;
padding: 15px;
font-size: 0;
}
')),
div(
style = "position: absolute; left: 50%; transform: translateX(-100%);",
div(
class = "wells",
DTOutput(id, width = "90%", height= "100%")
)
)
)
}
####Create the matrix and organization for the 96 well plate####
renderPlate96 = function(id, colors = rep("white", 108)) {
plate <- matrix(1:108,
nrow = 9,
ncol = 12,
byrow = TRUE,
dimnames = list(LETTERS[1:9], 1:12))
colnames (plate) = stringr::str_pad(colnames(plate), 2, "left", "0")
return(plate_return1 <-
datatable(
plate,
options = list(dom = 't', ordering = F),
selection = list(mode = 'multiple',
target = "cell"),
class = 'cell-border compact'
) %>%
formatStyle(
1:12,
cursor = 'pointer',
backgroundColor = styleEqual(1:108, colors, default = NULL)
)
)
}
# app code
ui <- fluidPage(
useShinyjs(),
plate96("plate"),
tags$b("Wells Selected:"),
DTOutput("selected_table"),
verbatimTextOutput("plateWells_selected"),
br(),
helpText("Step 1: Add in a couple of buttons"),
numericInput("num_conds",
label = h3("Enter the number of treatments/ conditions"),
min = 1,
max = 20,
value = 1),
htmlOutput("cond_buttons", align = 'center'),
helpText("Step 2: Type in any name for a condition for the buttons"),
uiOutput("boxes_conds"),
helpText("Step 3: Choose any color for the buttons"),
uiOutput("cond_colors"),
helpText("Step 4: Select cells from the table above"),
DTOutput("storage_table"),
DT::dataTableOutput("colors_table"),
DT::dataTableOutput("conds_table"),
)
server <- function(input, output, session){
### **** Problem Area **** ####
observeEvent(req(input$plate_cells_selected), {
delay(500,
for(i in 1:nrow(selected$df)) {
#For selecting any individual cells, coordinating the indices between the cell_selected and the conds and colors data.frames
if (selected$df[i,1] < 9 && selected$df[i,2] > 0) {
colors_df[selected$df[i,1], selected$df[i,2]] <- selected$df[i,3]
conds_df[selected$df[i,1], selected$df[i,2]] <- selected$df[i,4]
}
#For selecting all cells in a row if the letters are selected
else if (selected$df[i,2] == 0) {
for(x in seq(12)) {
colors_df[selected$df[i,1], x] <- selected$df[i,3]
conds_df[selected$df[i,1], x] <- selected$df[i,4]
}
}
#For selecting all the cells in a column if the 9th cell is selected
else if (selected$df[i,1] == 9) {
for(x in seq(8)) {
colors_df[x, selected$df[i,2]] <- selected$df[i,3]
conds_df[x, selected$df[i,2]] <- selected$df[i,4]
}
}
}
)})
###End problem area###
####Storage data.frame for when the buttons are clicked####
storage <- (data.frame(
color_selected = NA,
cond_selected = NA
))
storage_df <- reactiveVal(storage)
observeEvent(input$num_conds, {
lapply(1:input$num_conds, function(x){
observeEvent(input[[paste0("cond_buttons",x)]], {
newdf <- data.frame(
color_selected = input[[paste0("colors",x)]],
cond_selected = input[[paste0("condID",x)]]
)
storage_df(newdf)
}, ignoreInit = TRUE)
})
})
output$storage_table <- renderDataTable(
req(storage_df()),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
selected <- reactiveValues(df=NULL,scope=NULL)
df1 <- data.frame()
observeEvent(input$plate_cells_selected, {
n = dim(req(input$plate_cells_selected))[1]
df1 <<- data.frame(rows = req(input$plate_cells_selected[,1]),
columns = req(input$plate_cells_selected[,2]))
###Create a DT that stores the values of the cells selected in the plate####
selected$scope <- rbind(selected$df,data.frame(rows = input$plate_cells_selected[n,1],
columns = input$plate_cells_selected[n,2],
color_selected = storage_df()[1,1],
cond_selected = storage_df()[1,2],
stringsAsFactors = FALSE))
}, ignoreNULL=FALSE)
observeEvent(selected$scope, {
n1 = dim(df1)[1]
n2 = dim(selected$scope)[1]
if (n1 > n2) { ## add a row
df <- selected$scope
}else df <- left_join(df1,
selected$scope,
by=c("rows","columns"))
selected$df <- df[!duplicated(df[,1:2]),]
})
output$selected_table <- renderDT(
selected$df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
lengthChange = FALSE,
stringsAsFactors = FALSE
)
)
####data.frame for color information####
colors_df <- data.frame(
matrix(ncol = 12, nrow = 8)
)
output$colors_table <- renderDataTable(
colors_df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
####data.frame for condition information####
conds_df <- data.frame(
matrix(ncol = 12, nrow = 8)
)
output$conds_table <- renderDataTable(
conds_df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
####Input for user browse and data upload####
output$contents <- renderTable({ req(input$data) })
#####Slider for frames per second####
output$value <- renderPrint({ input$Frames })
#####Check boxes for no-movement cell exclusion####
output$value <- renderPrint({ input$emptyWell_checkbox })
#####Number output for number of conditions#####
output$value <- renderPrint({ input$num_conds })
#### Condition boxes for UI text input####
output$boxes_conds <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
cond_names <- textInput(paste0("condID", i),
label = paste0("Treatment/ Conditions: ", i),
placeholder = "Enter condition..."
)
})
})
#### Color selection for UI input####
output$cond_colors <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
colourpicker::colourInput(paste0("colors", i),
label = (paste0("Select a color for condition ", i)),
show = c("both"),
value = "black",
palette = c("limited"),
)
})
})
#### Create action buttons for conditions to be selected####
output$cond_buttons <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
bg = input[[paste0("colors", i)]]
style = paste0(
collapse = " ",
glue("background-color:{bg};
color:#ffffff;
border-color:#000000")
)
label = input[[paste0("condID", i)]]
actionButton(paste0("cond_buttons", i),
label = label,
style = style,
)
})
})
####Create the 96 well plate image####
output$plate <- renderDT({
renderPlate96()
})
output$plateWells_selected <- renderPrint({
input$plate_cells_selected
})
}
shinyApp(ui = ui, server = server)
You have multiple issues here. You need reactive dataframes to display the last two tables. You can use local() in a for loop (to deal with lazy evaluation) or lapply in an observer. I have shown both examples here. Try this
library(shiny)
library(dplyr)
library(DT)
library(glue)
library(shinyWidgets)
library(colourpicker)
library(shinyjs)
####Create the matrix and organization for the 96 well plate####
plate96 <- function(id) {
div(
style = "position: relative; height: 500px",
tags$style(HTML('
.wells {
transform: translateX(50%);
}
.wells table.dataTable tr:nth-child(9) td { /*for the row 9, need to make it not look like a row*/
border-bottom: unset;
}
.wells tbody tr td:not(:first-of-type) {
border: 1px solid black;
height: 15px;
width: 15px;
padding: 15px;
font-size: 0;
}
')),
div(
style = "position: absolute; left: 50%; transform: translateX(-100%);",
div(
class = "wells",
DTOutput(id, width = "90%", height= "100%")
)
)
)
}
####Create the matrix and organization for the 96 well plate####
renderPlate96 = function(id, colors = rep("white", 108)) {
plate <- matrix(1:108,
nrow = 9,
ncol = 12,
byrow = TRUE,
dimnames = list(LETTERS[1:9], 1:12))
colnames (plate) = stringr::str_pad(colnames(plate), 2, "left", "0")
return(plate_return1 <-
datatable(
plate,
options = list(dom = 't', ordering = F),
selection = list(mode = 'multiple',
target = "cell"),
class = 'cell-border compact'
) %>%
formatStyle(
1:12,
cursor = 'pointer',
backgroundColor = styleEqual(1:108, colors, default = NULL)
)
)
}
# app code
ui <- fluidPage(
useShinyjs(),
plate96("plate"),
tags$b("Wells Selected:"),
DTOutput("selected_table"),
verbatimTextOutput("plateWells_selected"),
br(),
helpText("Step 1: Add in a couple of buttons"),
numericInput("num_conds",
label = h3("Enter the number of treatments/ conditions"),
min = 1,
max = 20,
value = 1),
htmlOutput("cond_buttons", align = 'center'),
helpText("Step 2: Type in any name for a condition for the buttons"),
uiOutput("boxes_conds"),
helpText("Step 3: Choose any color for the buttons"),
uiOutput("cond_colors"),
helpText("Step 4: Select cells from the table above"),
DTOutput("storage_table"),
DT::dataTableOutput("colors_table"),
DT::dataTableOutput("conds_table"),
)
server <- function(input, output, session){
### **** Problem Area **** ####
observeEvent(req(input$plate_cells_selected), {
delay(500,
for(i in 1:nrow(selected$df)) {
local({
i <- i
#For selecting any individual cells, coordinating the indices between the cell_selected and the conds and colors data.frames
if (selected$df[i,1] < 9 && selected$df[i,2] > 0) {
colors$df[selected$df[i,1], selected$df[i,2]] <- selected$df[i,3]
conds$df[selected$df[i,1], selected$df[i,2]] <- selected$df[i,4]
}
#For selecting all cells in a row if the letters are selected
else if (selected$df[i,2] == 0) {
lapply(1:12, function(x){
#for(x in seq(12)) {
colors$df[selected$df[i,1], x] <- selected$df[i,3]
conds$df[selected$df[i,1], x] <- selected$df[i,4]
#}
})
}
#For selecting all the cells in a column if the 9th cell is selected
else if (selected$df[i,1] == 9) {
lapply(1:8, function(x){
#for(x in seq(8)) {
colors$df[x, selected$df[i,2]] <- selected$df[i,3]
conds$df[x, selected$df[i,2]] <- selected$df[i,4]
#}
})
}
})
}
)})
###End problem area###
####Storage data.frame for when the buttons are clicked####
storage <- (data.frame(
color_selected = NA,
cond_selected = NA
))
storage_df <- reactiveVal(storage)
observeEvent(input$num_conds, {
lapply(1:input$num_conds, function(x){
observeEvent(input[[paste0("cond_buttons",x)]], {
newdf <- data.frame(
color_selected = input[[paste0("colors",x)]],
cond_selected = input[[paste0("condID",x)]]
)
storage_df(newdf)
}, ignoreInit = TRUE)
})
})
output$storage_table <- renderDataTable(
req(storage_df()),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
selected <- reactiveValues(df=NULL,scope=NULL)
df1 <- data.frame()
observeEvent(input$plate_cells_selected, {
n = dim(req(input$plate_cells_selected))[1]
df1 <<- data.frame(rows = req(input$plate_cells_selected[,1]),
columns = req(input$plate_cells_selected[,2]))
###Create a DT that stores the values of the cells selected in the plate####
selected$scope <- rbind(selected$df,data.frame(rows = input$plate_cells_selected[n,1],
columns = input$plate_cells_selected[n,2],
color_selected = storage_df()[1,1],
cond_selected = storage_df()[1,2],
stringsAsFactors = FALSE))
}, ignoreNULL=FALSE)
observeEvent(selected$scope, {
n1 = dim(df1)[1]
n2 = dim(selected$scope)[1]
if (n1 > n2) { ## add a row
df <- selected$scope
}else df <- left_join(df1,
selected$scope,
by=c("rows","columns"))
selected$df <- df[!duplicated(df[,1:2]),]
})
output$selected_table <- renderDT(
selected$df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
lengthChange = FALSE,
stringsAsFactors = FALSE
)
)
####data.frame for color information####
colors <- reactiveValues(df = data.frame(
matrix(ncol = 12, nrow = 8)
))
output$colors_table <- renderDataTable(
colors$df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
####data.frame for condition information####
conds <- reactiveValues(df = data.frame(
matrix(ncol = 12, nrow = 8)
))
output$conds_table <- renderDataTable(
conds$df,
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE
)
)
####Input for user browse and data upload####
output$contents <- renderTable({ req(input$data) })
#####Slider for frames per second####
output$value <- renderPrint({ input$Frames })
#####Check boxes for no-movement cell exclusion####
output$value <- renderPrint({ input$emptyWell_checkbox })
#####Number output for number of conditions#####
output$value <- renderPrint({ input$num_conds })
#### Condition boxes for UI text input####
output$boxes_conds <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
cond_names <- textInput(paste0("condID", i),
label = paste0("Treatment/ Conditions: ", i),
placeholder = "Enter condition..."
)
})
})
#### Color selection for UI input####
output$cond_colors <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
colourpicker::colourInput(paste0("colors", i),
label = (paste0("Select a color for condition ", i)),
show = c("both"),
value = "black",
palette = c("limited"),
)
})
})
#### Create action buttons for conditions to be selected####
output$cond_buttons <- renderUI({
num_conds = as.integer(input$num_conds)
lapply(1:num_conds, function(i) {
bg = input[[paste0("colors", i)]]
style = paste0(
collapse = " ",
glue("background-color:{bg};
color:#ffffff;
border-color:#000000")
)
label = input[[paste0("condID", i)]]
actionButton(paste0("cond_buttons", i),
label = label,
style = style,
)
})
})
####Create the 96 well plate image####
output$plate <- renderDT({
renderPlate96()
})
output$plateWells_selected <- renderPrint({
input$plate_cells_selected
})
}
shinyApp(ui = ui, server = server)

In R Shiny, how to write a function that generates additional user inputs upon clicking an action button?

I'm working on an App that allows the user to optionally expand a base starting input (firstInput in the below MWE). SecondInput allows the user to vertically expand his assumptions (doesn't work in this MWE but in full App it runs extrapolations and interpolations, and it expands vertically fitting well in a sidebar panel). ThirdInput below is neutered for illustration simplicity. FourthInput, appearing in modalDialog, allows user to expand assumptions horizontally. The inputs are sequentially chained (firstInput -> secondInput -> fourthInput) with the last input taking precedence. Chaining works fine.
In full App I have vertical expansion working. Now I need help with horizontal assumption expansion.
As shown in the image at the bottom, in the modalDialog, how can I have a click of the "Add scenario" actionButton add another input matrix to the right, called "fifthInput"? Another click would add "sixthInput" to the right, etc. This is what I mean by "horizontal expansion". As far as chaining, these new inputs matrices would be chained to secondInput just like fourthInput is. A click of the "Remove above" actionButton would remove the input matrix immediately above it. I'm not sure how large a modalDialog box expands but I may need some kind of box that allows vertical/horizontal scrolling. If this is a bit much, I wonder if there's some sort of package that does or helps with this.
MWE code:
library(shiny)
library(shinyjs)
library(shinyMatrix)
f <- function(action,i){as.character(checkboxInput(paste0(action,i),label=NULL))}
actions <- c("show", "reset")
tbl <- t(outer(actions, c(1,2), FUN = Vectorize(f)))
colnames(tbl) <- c("Show", "Reset")
rownames(tbl) <- c("2nd input", "3rd input")
firstInput <- function(inputId){
matrixInput(inputId,
value = matrix(c(5), 1, 1, dimnames = list(c("1st input"),NULL)),
rows = list(extend = FALSE, names = TRUE),
cols = list(extend = FALSE, names = FALSE, editableNames = FALSE),
class = "numeric")}
secondInput <- function(inputId,x){
matrixInput(inputId,
value = matrix(c(x), 1, 1, dimnames = list(c("2nd input"),NULL)),
rows = list(extend = FALSE, names = TRUE),
cols = list(extend = FALSE, names = FALSE, editableNames = FALSE),
class = "numeric")}
fourthInput <- function(inputId,x){
matrixInput(inputId,
value = matrix(c(x), 1, 1, dimnames = list(c("4th input"),NULL)),
rows = list(extend = FALSE, names = TRUE),
cols = list(extend = FALSE, names = FALSE, editableNames = FALSE),
class = "numeric")}
ui <- fluidPage(
tags$head(
tags$style(HTML(
"td .checkbox {margin-top: 0; margin-bottom: 0;}
td .form-group {margin-bottom: 0;}"
))
),
br(),
sidebarLayout(
sidebarPanel(
uiOutput("panel"),
hidden(uiOutput("secondInput")),
actionButton("showFourth","Show 4th input (in modal)",width = "100%") # ADDED
),
mainPanel(plotOutput("plot1"))
)
)
server <- function(input, output){
input1 <- reactive(input$input1)
input2 <- reactive(input$input2)
input4 <- reactive(input$input4)
output$panel <- renderUI({
tagList(
useShinyjs(),
firstInput("input1"),
strong(helpText("Generate curves (Y|X):")),
tableOutput("checkboxes")
)
})
output[["checkboxes"]] <-
renderTable({tbl},
rownames = TRUE, align = "c",
sanitize.text.function = function(x) x
)
observeEvent(input[["show1"]], {
if(input[["show1"]]){shinyjs::show("secondInput")} else
{shinyjs::hide("secondInput")}
})
observeEvent(input$showFourth,{
showModal(
modalDialog(
column(4,
actionButton("add","Add scenario"), div(style = "margin-bottom: 10px"),
fourthInput("input4",if(isTruthy(input$input4)){input$input4} else {input$input2[1,1]}),
actionButton("remove","Remove above")
),
footer = modalButton("Close")
)) # close showModal and modalDialog
})
output$secondInput <- renderUI({
req(input1())
secondInput("input2",input$input1[1,1])
})
outputOptions(output,"secondInput",suspendWhenHidden = FALSE)
output$plot1 <-renderPlot({
req(input2())
plot(rep(if(isTruthy(input$input4)){input4()} else {input2()}, times=5))
})
}
shinyApp(ui, server)
I always underestimate package shinyMatrix, turns out it has the horizontal extension feature I'm looking for and extensions can be grouped in 2's as I need. See modified MWE code reflecting this usage of shinyMatrix for extensions. Basically for the column specifications for matrixInput (in custom function fourthInput), all I did was add extend = TRUE, delta = 2, delete = TRUE, .... Extend means the matrix can be expanded (column-wise since this is in the column parameters section), delta of 2 = matrix expands in grouping of 2, delete = columns can be deleted.
However shinyMatrix output isn't the prettiest thing out there, I'm open to other solutions or packages!!
MWE code:
library(shiny)
library(shinyjs)
f <- function(action,i){as.character(checkboxInput(paste0(action,i),label=NULL))}
actions <- c("show", "reset")
tbl <- t(outer(actions, c(1,2), FUN = Vectorize(f)))
colnames(tbl) <- c("Show", "Reset")
rownames(tbl) <- c("2nd input", "3rd input")
xDflt <- 10
yDflt <- 5
userInput <- function(inputId,x,y,z){
matrixInput(inputId,
value = matrix(c(x,y), 1, 2, dimnames = list(c(z),c("X and Y",""))),
rows = list(extend = FALSE, names = TRUE),
cols = list(
extend = FALSE,
names = TRUE,
editableNames = FALSE,
multiheader=TRUE
),
class = "numeric")}
fourthInput <- function(inputId,x,y,z){
matrixInput(inputId,
value = matrix(c(x,y), 1, 2, dimnames = list(c(z),c("X and Y",""))),
label = "Add, delete, or modify matrix parameters:",
rows = list(extend = FALSE, names = TRUE),
cols = list(
extend = TRUE,
delta = 2,
delete = TRUE,
names = TRUE,
editableNames = FALSE,
multiheader=TRUE
),
class = "numeric")}
ui <- fluidPage(
tags$head(
tags$style(HTML(
"td .checkbox {margin-top: 0; margin-bottom: 0;}
td .form-group {margin-bottom: 0;}"
))
),
br(),
sidebarLayout(
sidebarPanel(
uiOutput("panel"),
hidden(uiOutput("secondInput")),
actionButton("showFourth","Show 4th input (in modal)",width = "100%")
),
mainPanel(plotOutput("plot1"))
)
)
server <- function(input, output){
input1 <- reactive(input$input1)
input2 <- reactive(input$input2)
input4 <- reactive(input$input4)
output$panel <- renderUI({
tagList(
useShinyjs(),
userInput("input1",xDflt,yDflt,"1st input"),
strong(helpText("Generate curves (Y|X):")),
tableOutput("checkboxes")
)
})
output[["checkboxes"]] <-
renderTable({tbl},
rownames = TRUE, align = "c",
sanitize.text.function = function(x) x
)
observeEvent(input[["show1"]], {
if(input[["show1"]]){shinyjs::show("secondInput")} else
{shinyjs::hide("secondInput")}
})
observeEvent(input$showFourth,{
showModal(
modalDialog(
fourthInput("input4",
xDflt,
if(isTruthy(input$input4)){input$input4[1,2]} else
{input$input2[1,2]},
"4th input"),
footer = modalButton("Close")
))
})
output$secondInput <- renderUI({
req(input1())
userInput("input2",xDflt,input$input1[1,2],"2nd Input")
})
outputOptions(output,"secondInput",suspendWhenHidden = FALSE)
output$plot1 <-renderPlot({
req(input2())
plot(rep(if(isTruthy(input$input4)){input4()[1,2]} else {input2()[1,2]}, times=10))
})
}
shinyApp(ui, server)

How to use the data rendered in output through DT::renderDataTable for plotting

I have created a datatable with renderDT and reactive functions, in order to change the table with selectInputs. Now I want to plot a geom_line graphic with the datatable created and have a reactive dashboard that have to change with the same selectInputs, but I don't know how. If you have some ideas please share. In addition I want to have no default selection in my selectInputs.
Here is the code:
library(shiny)
library(DT)
library(dplyr)
library(shinyWidgets)
library(tidyverse)
library(lubridate)
library(timetk)
library(ggplot2)
library(rJava)
library(xlsx)
library(graphics)
data_1 <-mtcars
# User Interface
ui <- fluidPage(
titlePanel("My dashboard"),
sidebarLayout(
sidebarPanel(
selectInput('filter_gear', 'gear', choices = NULL, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL),
selectInput('filter_carb', 'carb', choices = NULL, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL),
selectInput('filter_cyl', 'cyl', choices = NULL, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL)
),
mainPanel(
tabsetPanel(id="mydash", type= "tabs",
tabPanel("Plot", plotOutput("fig"), plotOutput("fig2"), plotOutput("fig3")),
tabPanel("Tables", p(DTOutput('databasedf')))
)
)
)
)
server <- function(input, output, session) {
filterdf <- reactive({
filterdf <- data_1
filterdf <- droplevels.data.frame(filterdf)
return(filterdf)
})
filtergear <- reactive({
unique(as.character(filterdf()$gear))
})
observeEvent(filtergear(), {
updateSelectInput(session,
"filter_gear",
choices = filtergear(),
selected = sort(filtergear()))
})
# # Subset dynamically the previous reactive filter #
datasub1 <- reactive({
data_1[data_1$gear %in% input$filter_gear,]
})
filtercarb <- reactive({
unique(as.character(datasub1()$carb))
})
observeEvent(filtercarb(), {
updateSelectInput(session,
"filter_carb",
choices = sort(filtercarb()),
selected = sort(filtercarb()))
})
# Subset dynamically the previous reactive filter #
datasub2 <- reactive({
# browser()
data_1[data_1$carb %in% input$filter_carb,]
})
filtercyl <- reactive({
unique(as.character(datasub2()$cyl))
})
observeEvent(filtercyl(), {
updateSelectInput(session,
"filter_cyl",
choices = sort(filtercyl()),
selected = sort(filtercyl()))
})
output$databasedf <- DT::renderDT({
# Subset for plotly reactivity
Filter1 <- droplevels.data.frame(data_1)
Filter2 <- filter(Filter1,
Filter1$gear %in% input$filter_gear,
Filter1$carb %in% input$filter_carb,
Filter1$cyl %in% input$filter_cyl)
# Plot
datatable(Filter2,
filter="none",
selection="none",
escape=FALSE,
rownames = FALSE,
# colnames = c("", ""),
autoHideNavigation = TRUE,
style = 'bootstrap4',
options = list(searching = FALSE, # remove search option
ordering = FALSE, # remove sort option
paging = FALSE, # remove paging
info = FALSE # remove bottom information
)) %>%
formatStyle(columns = 1, fontWeight = 'bold', `text-align` = 'left') # text to bold and lign left in first column
})
output$fig <-renderPlot({
plt <- addDataFrame(Filter2, sheet, col.names=TRUE, row.names=TRUE, startRow=1, startColumn=1)
fig <- plt %>% ggplot() + geom_line(aes(x=hp, y=mean(mpg), color=am)) })
}
shinyApp(ui, server)
I defined Filter2 outside renderDT to allow renderPlot find it. I left plt commented. I tried to leave the app without major changes. The req s before ggplot are to avoid an error at the start (because the inputs are not updated yet with the select options).
library(shiny)
library(DT)
library(dplyr)
library(shinyWidgets)
library(tidyverse)
library(lubridate)
library(timetk)
library(ggplot2)
library(rJava)
library(xlsx)
library(graphics)
data_1 <- mtcars
# User Interface
ui <- fluidPage(
titlePanel("My dashboard"),
sidebarLayout(
sidebarPanel(
selectInput('filter_gear', 'gear', choices = NULL, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL),
selectInput('filter_carb', 'carb', choices = NULL, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL),
selectInput('filter_cyl', 'cyl', choices = NULL, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL)
),
mainPanel(
tabsetPanel(id="mydash", type= "tabs",
tabPanel("Plot", plotOutput("fig"), plotOutput("fig2"), plotOutput("fig3")),
tabPanel("Tables", p(DTOutput('databasedf')))
)
)
)
)
server <- function(input, output, session) {
filterdf <- reactive({
filterdf <- data_1
filterdf <- droplevels.data.frame(filterdf)
return(filterdf)
})
filtergear <- reactive({
unique(as.character(filterdf()$gear))
})
observeEvent(filtergear(), {
updateSelectInput(session,
"filter_gear",
choices = filtergear(),
selected = sort(filtergear()))
})
# # Subset dynamically the previous reactive filter #
datasub1 <- reactive({
data_1[data_1$gear %in% input$filter_gear,]
})
filtercarb <- reactive({
unique(as.character(datasub1()$carb))
})
observeEvent(filtercarb(), {
updateSelectInput(session,
"filter_carb",
choices = sort(filtercarb()),
selected = sort(filtercarb()))
})
# Subset dynamically the previous reactive filter #
datasub2 <- reactive({
# browser()
data_1[data_1$carb %in% input$filter_carb,]
})
filtercyl <- reactive({
unique(as.character(datasub2()$cyl))
})
observeEvent(filtercyl(), {
updateSelectInput(session,
"filter_cyl",
choices = sort(filtercyl()),
selected = sort(filtercyl()))
})
Filter2 <- reactive({
# Subset for plotly reactivity
Filter1 <- droplevels.data.frame(data_1)
filter(Filter1,
Filter1$gear %in% input$filter_gear,
Filter1$carb %in% input$filter_carb,
Filter1$cyl %in% input$filter_cyl)
})
output$databasedf <- DT::renderDT({
datatable(Filter2(),
filter="none",
selection="none",
escape=FALSE,
rownames = FALSE,
# colnames = c("", ""),
#autoHideNavigation = TRUE,
style = 'bootstrap4',
options = list(searching = FALSE, # remove search option
ordering = FALSE, # remove sort option
paging = FALSE, # remove paging
info = FALSE # remove bottom information
)) %>%
formatStyle(columns = 1, fontWeight = 'bold', `text-align` = 'left') # text to bold and lign left in first column
})
output$fig <-renderPlot({
req(input$filter_gear)
req(input$filter_carb)
req(input$filter_cyl)
#plt <- addDataFrame(reac_filter$Filter2, sheet, col.names=TRUE, row.names=TRUE, startRow=1, startColumn=1)
ggplot() + geom_line(aes(x=hp, y=mean(mpg), color=am),data = Filter2()) })
}
shinyApp(ui, server)

how to make a copy of reactive table in R shiny in `reactiveValues()`

I am building app where a user can make edits to a datatable and the hit a button to reflect the changes in a non-editable copy of this datatable (in the final project, I will need to have two datasets that need to be matched manually), but for now this small MWE shows the problem I have with making a copy of the reactive table in which changes can be made, without changing the data of the original reactive table. I would like to make this app work, where you click edit a cell in the table dat_joined$data/output$mytable and that those changes do reflect in a new table mydf$data/output$table2. To do mydf$data initially (before any changes are made) needs to be a copy of dat_joined$data This is a follow up on this question and answer: how to make a copy of a reactive value in shiny server function
library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)
# create master dataframe
dat_total <- tibble(ID_1 = 1:10, names = letters[1:10],
ID_2 = 11:20, names_2 = LETTERS[c(3:5, 1, 2, 6:8, 10, 9)])
shinyApp(
ui = fluidPage(
title = 'Radio button and a dropdown manue ',
sliderInput("n_rows_table", "Number of rows:",
min = 0, max = 10,
value = 5),
actionBttn(
inputId = "button_1",
label = "Make tables",
size = "sm",
color = "warning"
),
DT::dataTableOutput("mytable"),
actionBttn(
inputId = "button_2",
label = "Process",
size = "sm",
color = "success"),
DT::dataTableOutput("table2")),
server = function(input, output, session) {
# set up reactive values
dat_left <- reactiveValues(data=NULL)
dat_right <- reactiveValues(data=NULL)
dat_joined <- reactiveValues(data=NULL)
# create reactive daraframe
dat <- eventReactive(input$button_1, {
dat_total[1:input$n_rows_table, ] %>%
rowid_to_column()})
# Split the data into a right and a left set
observe({
dat_left$data <- dat() %>%
select(rowid, ID_1, names)
})
observe({
dat_right$data <- dat() %>%
select(rowid, ID_2, names_2,ID_1)
})
# join these again
# This is needed because my actual app will
# be used to manually match 2 datasets
observe({
if (is.null( dat_right$data )) {
NULL
}else{
dat_joined$data <- left_join(dat_left$data,
dat_right$data,
by = "rowid")
}
})
# Print the the datasets
output$mytable <- renderDT({
datatable(dat_joined$data ,
rownames = F,
editable = "cell")
})
# I want to make a copy of the dat_joined$data dataset into dat$mydf
# none of these function as expected
#mydf <- reactiveValues(data=isolate(dat_joined$data))
#mydf <- reactiveValues(data=local(dat_joined$data))
#mydf <- reactiveValues(data=dat_joined$data)
#mydf <- reactiveValues(data=NULL)
# This works, but only saves the cells to w
mydf <- reactiveValues(data=matrix(NA, nrow=10, ncol = 5))
# Ideally the computation only happens when this both an edit is made
# and the button is pressed (now I need to press it between every edit)
# validate_event <- reactive({
# req(input$mytable_cell_edit) & req(input$button_2)
# })
#observeEvent(input$button_2validate_event(), { DOes not work
observeEvent(input$button_2,{
info = input$mytable_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
mydf$data[i, j] <- DT::coerceValue(v, mydf$data[i, j])
})
# print
output[["table2"]] <- renderDT({
datatable(mydf$data)
})
}
)
Any changes you make in the top table is reflected in the bottom table after you press the button "Process". Try this
library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)
# create master dataframe
dat_total <- tibble(ID_1 = 1:10, names = letters[1:10],
ID_2 = 11:20, names_2 = LETTERS[c(3:5, 1, 2, 6:8, 10, 9)])
shinyApp(
ui = fluidPage(
title = 'Radio button and a dropdown manue ',
sliderInput("n_rows_table", "Number of rows:",
min = 0, max = 10,
value = 5),
actionBttn(
inputId = "button_1",
label = "Make tables",
size = "sm",
color = "warning"
),
DT::dataTableOutput("mytable"),
actionBttn(
inputId = "button_2",
label = "Process",
size = "sm",
color = "success"),
DT::dataTableOutput("table2")),
server = function(input, output, session) {
# set up reactive values
dat_left <- reactiveValues(data=NULL)
dat_right <- reactiveValues(data=NULL)
dat_joined <- reactiveValues(data=NULL)
dfon <- reactiveValues(top=NULL,
bottom=NULL)
# create reactive daraframe
dat <- eventReactive(input$button_1, {
dat_total[1:input$n_rows_table, ] %>%
rowid_to_column()})
# Split the data into a right and a left set
observe({
req(dat())
dat_left$data <- dat() %>%
dplyr::select(rowid, ID_1, names)
})
observe({
req(dat())
dat_right$data <- dat() %>%
dplyr::select(rowid, ID_2, names_2,ID_1)
})
# join these again
# This is needed because my actual app will
# be used to manually match 2 datasets
observe({
req(dat())
if (!is.null( dat_right$data )) {
dat_joined$data <- left_join(dat_left$data,
dat_right$data,
by = "rowid")
}
})
observe({ ###assign your orig data to a reactiveValues object
req(dat_joined$data)
if (!is.null(dat_joined$data)) {
dfon$top <- dat_joined$data
}
})
# Print the the datasets
output$mytable <- renderDT({
datatable(dfon$top,
rownames = F,
editable = "cell")
})
# Ideally the computation only happens when this both an edit is made
# and the button is pressed (now I need to press it between every edit)
observeEvent(input$mytable_cell_edit, {
info = input$mytable_cell_edit
str(info)
#i = info$row
#j = info$col + 1 # offset by 1
#v = info$value
#dfon$top[i, j] <<- DT::coerceValue(v, dfon$top[i, j])
dfon$top <<- editData(dfon$top, info)
})
observeEvent(input$button_2,{
dfon$bottom <- dfon$top
output$table2 <- renderDT({
datatable(dfon$bottom)
})
})
## further editing of dfon$bottom is performed below...with...observeEvent(input$table2_cell_edit, {...
}
)
In the output below, I have entered cccc for 3rd element in names column, but I have not clicked on the button Process. Therefore, the edited cell is not reflected in the bottom table.

Resources