--- output: html_document runtime: shiny_prerendered author: Environmental Cheminformatics Group, LCSB, University of Luxembourg title: "`r paste('Shinyscreen', packageVersion('shinyscreen'))`" --- ```{r, context='setup', include='false'} library(data.table) library(shinyscreen) def_state <- new_state() def_datafiles <- shinyscreen:::dtable(File=character(0), tag=character(0)) def_datatab <- shinyscreen:::dtable("tag"=factor(), "adduct"=factor(levels=shinyscreen:::DISP_ADDUCTS), "set"=factor()) def_summ_subset <- shinyscreen:::dtable("QA Column"=shinyscreen:::QA_FLAGS, "Select"=factor("il irrilevante",levels=c("il irrilevante", "il buono", "il cattivo"))) ## RMassBank masks shiny::validate. Unmask it. validate <- shiny::validate ## def_state$input$tab$tags <- def_datatab rv_state <- list2rev(def_state) compl_sets <- eventReactive(rv_state$input$tab$setid, rv_state$input$tab$setid[,unique(set)]) ## Reactive values to support some of the UI elements. ## rv_ui <- reactiveValues(datatab=def_tags) ## Update with data-files. rv_dfiles <- reactiveVal(def_datafiles) ## Data-file table when loading. rv_datatab <- reactiveVal(def_datatab) ``` # Configuration {.tabset} ## Inputs <details> <summary>Specify the project directory</summary> This is where the output files and the state of the analysis will be saved. </details> ```{r, echo=FALSE} actionButton(inputId = "project_b", label= "Project") ``` Current project directory is `r textOutput("project", inline=T)` <details><summary>Load the compound list(s)</summary> A compound list is composed of entries describing compounds. This description is used to search for its spectrum in the data file. The list is a table in the ***CSV*** format and contains these columns, * ***ID*** : required column, must be filled; this is a user-defined ID, uniquely associated with a compound * ***Name*** : this column can be left blank; if not, it should contain the names of the compounds * ***SMILES*** : a _SMILES_ string, describing the structure of the compound; this entry can be left empty only if one of either ***Formula***, or ***mz*** entries are not * ***Formula*** : a chemical formula of a compound; this field can be empty only if one of either ***SMILES***, or ***mz*** entries are not * ***mz*** : mass of the ionised compound; this field can be left empty only if one of either ***SMILES***, or ***Formula*** is not * ***CAS*** : the CAS number of the compound; it can be left empty * ***RT*** : retention time of the MS1 peak in minutes, if known; can be left empty. Only ***ID*** and one of ***SMILES***, ***Formula*** or ***mz*** must be filled. When structure, or a formula of a compound is known, it is also possible to look for various adducts in the sample. Of course, scanning for completely unknown compounds is also supported by the ***mz*** column. In this case, ***mz*** is the mass of the ion. It is strongly recommended to quote SMILES, names and formulas in the CSV file used with Shinyscreen. </details> ```{r, echo=FALSE} actionButton(inputId = "comp_list_b", label= "Compound list(s)") ``` `r htmlOutput("comp_lists")` <details><summary>Load compound set list (_setid_ table)</summary> The compound lists can contain more entries than is necessary. Using the _setid_ lists, it is possible to create _compound sets_ which contain only those compounds that will actually be searched for in the data files. A _setid table_ is a _CSV_ containing at least two columns, * ***ID*** : the ID entry from the compound list * ***set*** : an user-defined set name. </details> ```{r, echo=FALSE} actionButton(inputId = "setid_b", label= "Load the setid table") ``` `r htmlOutput("setids", inline=T)` ## Data files <details><summary>Load data files</summary> Shinyscreen currently supports only the **mzML** file format. After loading the files, set file tags in the file table (column **tag**). Additionally, specify a set of compounds that is supposed to be extracted from the file using the **set** column. Finally, specify the **adduct** in the adduct column. In case of compounds with unknown structure and formula, the adduct is ignored for obvious reasons. </details> ```{r, echo=FALSE} actionButton(inputId = "datafiles_b", label= "Load data files.") ``` <details><summary>Assign tags to data files.</summary> Each tag designates an unique file. Use the table below to assign tags. </details> ```{r, echo=FALSE} rhandsontable::rHandsontableOutput("datafiles") ``` <details><summary>Assign sets to tags.</summary> For each tag, assign a set and an adduct (if the structure information exists, otherwise _adduct_ column is ignored). </details> ```{r, echo=F} rhandsontable::rHandsontableOutput("datatab") ``` ## Extraction ### Spectra extraction based settings <details><summary>MS1 coarse error</summary> Extract all entries matching the target mass within this error in the precursor table. </details> ```{r, echo=F} shinyscreen::mz_input(input_mz = "ms1_coarse", input_unit = "ms1_coarse_unit", def_mz = def_state$conf$tolerance[["ms1 coarse"]], def_unit = "Da") ``` <details><summary>MS1 fine error</summary> The precursor table masses can be of lower accuracy. Once there is a match within the coarse error, it can be further checked versus the fine error bounds directly in the mass spectrum. </details> ```{r, echo=F} shinyscreen::mz_input(input_mz = "ms1_fine", input_unit = "ms1_fine_unit", def_mz = def_state$conf$tolerance[["ms1 fine"]], def_unit = "ppm") ``` <details><summary>MS1 EIC window</summary> The mz interval over which the intensities are aggregated to generate a chromatogram. </details> ```{r, echo=F} shinyscreen::mz_input(input_mz = "ms1_eic", input_unit = "ms1_eic_unit", def_mz = def_state$conf$tolerance[["eic"]], def_unit = "Da") ``` <details><summary>Retention time window</summary> If the expected retention time has been specified for the compound, then search for the MS1 signature inside the window defined by this range. </details> ```{r, echo=F} shinyscreen::rt_input(input_rt = "ms1_rt_win", input_unit = "ms1_rt_win_unit", def_rt = def_state$conf$tolerance[["rt"]], def_unit = "min") ``` ## Prescreening <details><summary>MS1 intensity threshold</summary> Ignore MS1 signal below the threshold. </details> ```{r, echo=F} numericInput(inputId = "ms1_int_thresh", label = NULL, value = def_state$conf$prescreen$ms1_int_thresh) ``` <details><summary>MS2 intensity threshold</summary> Ignore MS2 signal below the threshold. </details> ```{r, echo=F} numericInput(inputId = "ms2_int_thresh", label = NULL, value = def_state$conf$prescreen$ms2_int_thresh) ``` MS1 signal-to-noise ratio. ```{r, echo=F} numericInput(inputId = "s2n", label = NULL, value = def_state$conf$prescreen$s2n) ``` <details><summary>MS1/MS2 retention delay.</summary> Look for associated MS2 spectrum within this window around the MS1 peak. </details> ```{r, echo=F} shinyscreen::rt_input(input_rt = "ret_time_shift_tol", input_unit = "ret_time_shift_tol_unit", def_rt = def_state$conf$prescreen[["ret_time_shift_tol"]], def_unit = "min") ``` ## Filter and order the summary table <div style= "display: flex; vertical-align:top; "> <div style="padding-right: 0.5em"> <details><summary>Filter summary table</summary> Filter entries in the summary table according to the QA criteria. * **qa_pass** : entries that passed all checks * **qa_ms1_exists** : MS1 intensity is above the MS1 threshold * **qa_ms2_exists** : those entries for which some MS2 spectra have been found * **qa_ms1_above_noise** : MS1 is intense enough and above the noise level * **qa_ms2_good_int** : MS2 intensity is above the MS2 threshold * **qa_ms2_near** : MS2 spectrum is close enough to the MS1 peak For those who do not speak Italian (and do not dig the bad Sergio Leone pun): * **il irrelevante** : ignore QA criterion * **il buono** : entry passed QA * **il cattivo** : entry failed QA </details> ```{r, echo=F} rhandsontable::rHandsontableOutput("summ_subset") ``` </div> <div style="padding-left: 0.5em"> <details><summary>Ordering by columns</summary> It is possible to order the summary table using columns (keys): *`r paste(gsub("^-(.+)","\\1",shinyscreen:::DEF_INDEX_SUMM), collapse = ',')`*. The sequence of columns in the table below describes the sequence of ordering steps -- the key in the first row sorts the entire summary table and subsequent keys break the ties. </details> ```{r, echo=F} rhandsontable::rHandsontableOutput("order_summ") ``` </div> </div> ## Plots ### Logarithmic axis ```{r, echo=F} checkboxGroupInput("plot_log", label=NULL, choices = c("MS1 EIC","MS2 EIC","MS2 Spectrum"), selected = character(0)) ``` ### Global retention time range ```{r, echo=F} shinyscreen::rt_input(input_rt = "plot_rt_min", input_unit = "plot_rt_min_unit", def_rt = NA_real_, def_unit = "min", pref = "min:") shinyscreen::rt_input(input_rt = "plot_rt_max", input_unit = "plot_rt_max_unit", def_rt = NA_real_, def_unit = "min", pref = "max:") ``` ## Report ```{r, echo=F} shiny::textInput(inputId = "rep_aut", label = "Report author", value = def_state$conf$report$author) shiny::textInput(inputId = "rep_tit", label = "Report title", value = def_state$conf$report$title) ``` # View compound Lists and Sets {.tabset} ## Compound List ```{r, echo=F} DT::dataTableOutput("comp_table") ``` ## Setid Table ```{r, echo=F} DT::dataTableOutput("setid_table") ``` # Save and Restore Config Load the config file if needed. ```{r, echo=FALSE} actionButton(inputId = "conf_file_load_b", label= "Load project config") ``` Save the config file if needed. ```{r, echo=FALSE} actionButton(inputId = "conf_file_save_b", label= "Save config") ``` # Extract Data and Prescreen <details><summary>Extract spectra from data files.</summary> After Shinyscreen is configured, the compound and setid lists loaded, it is possible to proceed with extracting the data. This is potentially a time-intensive step, so some patience might be needed. Once the data is extracted, it will be possible to quality check the spectra associated with the compounds specified in the _setid_ list, to subset that data, look at the plots and publish a report. </details> ```{r, echo=FALSE} actionButton(inputId = "extract_b", label = "Extract") ``` # Browse Results <!-- ENGINE --> <!-- setup is here --> ```{r, include="false", context='setup'} ord_nms <- gsub("^-(.+)","\\1",shinyscreen:::DEF_INDEX_SUMM) ord_asc <- grepl("^-.+",shinyscreen:::DEF_INDEX_SUMM) ord_asc <- factor(ifelse(ord_asc, "descending", "ascending"),levels = c("ascending","descending")) def_ord_summ <- shinyscreen:::dtable("Column Name"=ord_nms,"Direction"=ord_asc) ``` ```{r, include="false", context='server'} ## reactive functions observeEvent(input$setid_b, { filters <- matrix(c("CSV files", ".csv", "All files", "*"), 2, 2, byrow = TRUE) setids <- tcltk::tk_choose.files(filters=filters) message("(config) Selected compound sets (setid): ", paste(setids,collapse = ",")) rv_state$conf$compounds$sets <- if (length(setids)>0 && nchar(setids[[1]])>0) setids else "Nothing selected." }) rf_compound_input_state <- reactive({ sets <- rv_state$conf$compounds$sets lst <- as.list(rv_state$conf$compounds$lists) validate(need(length(lst)>0, message = "Load the compound lists(s) first.")) validate(need(length(sets)>0 && nchar(sets)>0, message = "Load the setid table first.")) isolate({ state <- rev2list(rv_state) m <- load_compound_input(state) ## Side effect! This is because my pipeline logic does not ## work nicely with reactive stuff. rv_state$input$tab$cmpds <- list2rev(m$input$tab$cmpds) rv_state$input$tab$setid <- m$input$tab$setid m }) }) rf_conf_proj <- reactive({ state <- rev2list(rv_state) dir.create(state$conf$project,showWarnings = F) state }) rf_conf_state <- reactive({ state <- rf_conf_proj() ## mzml1 <- rf_get_inp_datatab() ## mzml1[,`:=`(tag=as.character(tag), ## set=as.character(set), ## adduct=as.character(adduct))] ## mzml2 <- rf_get_inp_datafiles() ## mzml <- mzml1[mzml2,on="tag"] ftab <- get_fn_ftab(state) state$conf$data <- ftab state$conf[["summary table"]]$filter <- rf_get_subset() state$conf[["summary table"]]$order <- rf_get_order() state }) rf_get_subset <- reactive({ dt <- tryCatch(rhandsontable::hot_to_r(input$summ_subset), error = function(e) def_summ_subset) dt[Select == "il buono", extra := T] dt[Select == "il cattivo", extra := F] sdt <- dt[!is.na(extra)] if (NROW(sdt) > 0) { sdt[,paste0(`QA Column`," == ",extra)] } else NULL }) rf_get_order <- reactive({ dt <- tryCatch(rhandsontable::hot_to_r(input$order_summ),error = function(e) def_ord_summ) tmp <- dt[Direction == "descending",.(`Column Name`=paste0("-",`Column Name`))] tmp[,`Column Name`] }) rf_get_inp_datatab <- eventReactive(input$datatab,{ z <- data.table::as.data.table(tryCatch(rhandsontable::hot_to_r(input$datatab)), error = function(e) def_datatab) z[,.(tag=as.character(tag), adduct=as.character(adduct), set=as.character(set)), with = T] }) rf_get_inp_datafiles <- eventReactive(input$datafiles,{ z <- data.table::as.data.table(tryCatch(rhandsontable::hot_to_r(input$datafiles)), error = function(e) def_datafiles) z[,.(File, tag=as.character(tag)), with = T] }) ``` ```{r, include="false", context='server'} observeEvent(input$project_b,{ wd <- tcltk::tk_choose.dir(default = getwd(), caption = "Choose project directory") message("Set project dir to ", wd) rv_state$conf$project <- wd }) observeEvent(input$comp_list_b, { filters <- matrix(c("CSV files", ".csv", "All files", "*"), 2, 2, byrow = TRUE) compfiles <- tcltk::tk_choose.files(filters=filters) message("(config) Selected compound lists: ", paste(compfiles,collapse = ",")) rv_state$conf$compounds$lists <- if (length(compfiles)>0 && nchar(compfiles[[1]])>0) compfiles else "Nothing selected." }) observeEvent(input$datafiles_b,{ filters <- matrix(c("mzML files", ".mzML", "All files", "*"), 2, 2, byrow = TRUE) fns <- tcltk::tk_choose.files(filters=filters) message("(config) Selected data files: ", paste(fns,collapse = ",")) ## Did the user choose any files? if (length(fns) > 0) { oldtab <- rf_get_inp_datafiles() newf <- setdiff(fns,oldtab$File) nr <- NROW(oldtab) tmp <- if (length(newf)>0) shinyscreen:::dtable(File=newf,tag=paste0('F',(nr+1):(nr + length(newf)))) else shinyscreen:::dtable(File=character(),tag=character()) z <- rbind(oldtab, tmp) z[,tag:=as.character(tag)] rv_dfiles(z) } }) observe({ df_tab <- rf_get_inp_datafiles() state <- rf_compound_input_state() isolate(oldtab <- rf_get_inp_datatab()) oldt <- oldtab$tag tagl <- df_tab$tag diff <- setdiff(tagl, oldt) res <- if (length(diff)!=0) { ## Only change the tag names in the old ones. pos_tag <- 1:length(tagl) pos_old <- 1:NROW(oldtab) pos_mod <- intersect(pos_tag,pos_old) new_tag <- tagl[pos_mod] if (NROW(oldtab)>0) oldtab[pos_mod,tag := ..new_tag] ## Now add tags for completely new files, if any. rest_new <- if (NROW(oldtab) > 0) setdiff(diff,new_tag) else diff tmp <- shinyscreen:::dtable(tag=rest_new, adduct=character(0), set=character(0)) dt <-data.table::as.data.table(rbind(as.data.frame(oldtab), as.data.frame(tmp))) dt[tag %in% df_tab$tag,] } else oldtab rv_datatab(res) }) observe({ dtab <- rv_datatab() dfiles <- rv_dfiles() message("(config) Generating mzml from rv.") isolate(rv_state$input$tab$mzml <- dtab[dfiles,on="tag"]) }, label = "mzml_from_rv") observe({ dtab <- rf_get_inp_datatab() dfiles <- rf_get_inp_datafiles() message("(config) Generating mzml from inputs.") res <- dtab[dfiles,on="tag"] data.table::setnames(res,"File","Files") isolate(rv_state$input$tab$mzml <- res) }, label = "mzml_from_inp") observeEvent(input$extract_b,{ m <- rf_conf_state() fn_c_state <- file.path(m$conf$project, paste0("extract.",shinyscreen:::FN_CONF)) yaml::write_yaml(x=m$conf,file=fn_c_state) message("(extract) Config written to ", fn_c_state) state <- withr::with_dir(new=m$conf$project, code = { m <- setup_phase(m) m <- mk_comp_tab(m) extr_data(m) }) message("(extract) Done extracting.") rv_state <- list2rev(state) }) observeEvent(input$conf_file_save_b, { state <- rf_conf_state() ftab <- get_fn_ftab(state) fconf <- get_fn_conf(state) yaml::write_yaml(state$conf, file = fconf) shinyscreen:::tab2file(tab=state$input$tab$mzml,file=ftab) message("Written data-file table to ",ftab) message("Written config to ",fconf) }) observeEvent(input$conf_file_load_b, { upd_unit <- function(entry,inp_val,inp_unit,choices) { if (isTruthy(entry)) { cntnt <- strsplit(entry,split = "[[:space:]]+")[[1]] cntnt <- cntnt[nchar(cntnt) > 0] if (length(cntnt)!=2) stop("(upd_unit) ","Unable to interpret ", entry) val <- cntnt[[1]] unit <- cntnt[[2]] updateNumericInput(session = session, inputId = inp_val, value = as.numeric(val)) updateSelectInput(session = session, inputId = inp_unit, selected = unit, choices = choices) } } upd_num <- function(entry,inp_val) { if (isTruthy(entry)) { updateNumericInput(session = session, inputId = inp_val, value = as.numeric(entry)) } } upd_sel <- function(inputId,selected,choices) { if (isTruthy(selected)) { updateSelectInput(session = session, inputId = inputId, selected = selected, choices = choices) } } filters <- matrix(c("YAML files", ".yaml", "All files", "*"), 2, 2, byrow = TRUE) fn <- tcltk::tk_choose.files(filters=filters, multi = F) message("(config) Loading config from: ", paste(fn,collapse = ",")) fn <- if (length(fn)>0 && nchar(fn[[1]])>0) fn else "" if (nchar(fn) > 0) { state <- new_state_fn_conf(fn) conf <- state$conf isolate({ rv_state$conf$project <- conf$project rv_state$conf$data <- conf$data ## Lists rv_state$conf$compounds$lists <- conf$compounds$lists rv_state$conf$compounds$sets <- conf$compounds$sets ## Tolerance upd_unit(conf$tolerance[["ms1 fine"]], "ms1_fine", "ms1_fine_unit", choices=c("ppm","Da")) upd_unit(conf$tolerance[["ms1 coarse"]], "ms1_coarse", "ms1_coarse_unit", choices=c("ppm","Da")) upd_unit(conf$tolerance[["eic"]], "ms1_eic", "ms1_eic_unit", choices=c("ppm","Da")) upd_unit(conf$tolerance[["rt"]], "ms1_rt_win", "ms1_rt_win_unit", choices=c("min","s")) ## Prescreen upd_num(conf$prescreen[["ms1_int_thresh"]], "ms1_int_thresh") upd_num(conf$prescreen[["ms2_int_thresh"]], "ms2_int_thresh") upd_num(conf$prescreen[["s2n"]], "s2n") upd_unit(conf$prescreen[["ret_time_shift_tol"]], "ret_time_shift_tol", "ret_time_shift_tol_unit", choices=c("min","s")) ## Files df <- shinyscreen:::file2tab(conf$data) df[,tag:=as.character(tag),with=T] rv_dfiles(df[,.(File=Files,tag)]) nms <- colnames(df) nms <- nms[nms!="Files"] fdt <- df[,..nms] rv_datatab(fdt) ## figures upd_unit(conf$figures$rt_min, "plot_rt_min", "plot_rt_min_unit", choices=c("min","s")) upd_unit(conf$figures$rt_max, "plot_rt_max", "plot_rt_max_unit", choices=c("min","s")) logentry <- conf$figures$logaxes logchoice <- logical(0) logchoice <- mapply(function(cn,uin) if (cn %in% logentry) uin else NA, c("ms1_eic_int","ms2_eic_int","ms2_spec_int"), c("MS1 EIC","MS2 EIC","MS2 Spectrum"),USE.NAMES = F) logchoice <- logchoice[!is.na(logchoice)] updateCheckboxGroupInput(session = session, inputId = "plot_log", choices = c("MS1 EIC", "MS2 EIC", "MS2 Spectrum"), selected = logchoice) ## Report if (isTruthy(conf$report$author)) updateTextInput(session,"rep_aut",value = conf$report$author) if (isTruthy(conf$report$title)) updateTextInput(session,"rep_tit",value = conf$report$title) }) } }) ``` <!-- Tolerance --> ```{r, include='false', context = 'server'} uni_ass <- function(val,unit) { paste(input[[val]], input[[unit]]) } observe({ rv_state$conf$tolerance[["ms1 fine"]] <- uni_ass("ms1_fine", "ms1_fine_unit") rv_state$conf$tolerance[["ms1 coarse"]] <- uni_ass("ms1_coarse", "ms1_coarse_unit") rv_state$conf$tolerance[["eic"]] <- uni_ass("ms1_eic", "ms1_eic_unit") rv_state$conf$tolerance[["rt"]] <- uni_ass("ms1_rt_win", "ms1_rt_win_unit") }) ``` <!-- Prescreen --> ```{r, include='false', context = 'server'} ## uni_ass <- function(val,unit) { ## paste(input[[val]], ## input[[unit]]) ## } observe({ rv_state$conf$prescreen[["ms1_int_thresh"]] <- input[["ms1_int_thresh"]] rv_state$conf$prescreen[["ms2_int_thresh"]] <- input[["ms2_int_thresh"]] rv_state$conf$prescreen[["s2n"]] <- input$s2n rv_state$conf$prescreen[["ret_time_shift_tol"]] <- uni_ass("ret_time_shift_tol", "ret_time_shift_tol_unit") }) ``` <!-- Plotting --> ```{r, include='false', context = 'server'} observe({ vals <- input$plot_log checked <- c("MS1 EIC"=F, "MS2 EIC"=F, "MS2 Spectrum"=F) if (length(vals)!=0) checked[vals] <- T l <- list() l <- c(if (checked[["MS1 EIC"]]) "ms1_eic_int" else NULL,l) l <- c(if (checked[["MS2 EIC"]]) "ms2_eic_int" else NULL,l) l <- c(if (checked[["MS2 Spectrum"]]) "ms2_spec_int" else NULL,l) rv_state$conf$figures[["logaxes"]] <- l[!sapply(l,is.null)] rv_state$conf$figures$rt_min <- uni_ass("plot_rt_min","plot_rt_min_unit") rv_state$conf$figures$rt_max <- uni_ass("plot_rt_max","plot_rt_max_unit") }) ``` <!-- Report --> ```{r, include='false', context = 'server'} observe({ rv_state$conf$report$author <- input$rep_aut rv_state$conf$report$title <- input$rep_tit }) ``` <!-- Render --> ```{r, include="false", context="server"} output$project <- renderText(rv_state$conf$project) output$comp_lists <- renderText({ lsts <- rev2list(rv_state$conf$compounds$lists) if (length(lsts) > 0 && isTruthy(lsts) && lsts != "Nothing selected.") { paste(c("<ul>", sapply(lsts, function (x) paste("<li>",x,"</li>")), "</ul>")) } else "No compound list selected yet." }) output$setids <- renderText({ sets <- rv_state$conf$compounds$sets if (isTruthy(sets) && sets != "Nothing selected.") paste("selected <em>setid</em> table:", sets) else "No <em>setid</em> table selected." }) output$order_summ <- rhandsontable::renderRHandsontable(rhandsontable::rhandsontable(def_ord_summ, manualRowMove = T)) output$datafiles <- rhandsontable::renderRHandsontable( { res <- rv_dfiles() rhandsontable::rhandsontable(as.data.frame(res), width = "50%", height = "25%", allowInvalid=F) }) output$datatab <- rhandsontable::renderRHandsontable( { setid <- rv_state$input$tab$setid res <- rv_datatab() if (NROW(res)>0) { res$tag <- factor(res$tag, levels = c(unique(res$tag), "invalid")) res$set <- factor(res$set, levels = c(unique(setid$set), "invalid")) res$adduct <- factor(res$adduct, levels = shinyscreen:::DISP_ADDUCTS) } rhandsontable::rhandsontable(res,stretchH="all", allowInvalid=F) }) output$comp_table <- DT::renderDataTable({ state <- rf_compound_input_state() DT::datatable(state$input$tab$cmpds, style = 'bootstrap', class = 'table-condensed', extensions = 'Scroller', options = list(scrollX = T, scrollY = 200, deferRender = T, scroller = T)) }) output$setid_table <- DT::renderDataTable({ state <- rf_compound_input_state() DT::datatable(state$input$tab$setid, style = 'bootstrap', class = 'table-condensed', extensions = 'Scroller', options = list(scrollX = T, scrollY = 200, deferRender = T, scroller = T)) }) output$summ_subset <- rhandsontable::renderRHandsontable({ rhandsontable::rhandsontable(def_summ_subset) }) ``` ```{r, echo=F, context = 'server'} session$onSessionEnded(function () stopApp()) ```