From 9011d95bf541d9dcb8f79e19bcb4a157207e47f5 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 17 Jun 2022 12:49:28 +0200 Subject: [PATCH 1/8] extract selectDataUI function, add new tabPanel "Merge Data", new module mergeData --- .Rbuildignore | 1 + DESCRIPTION | 2 +- R/02-importData.R | 19 +++++++++++++++++++ R/02-module-mergeData.R | 25 +++++++++++++++++++++++++ man/mergeDataServer.Rd | 11 +++++++++++ man/mergeDataUI.Rd | 14 ++++++++++++++ man/selectDataUI.Rd | 14 ++++++++++++++ 7 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 R/02-module-mergeData.R create mode 100644 man/mergeDataServer.Rd create mode 100644 man/mergeDataUI.Rd create mode 100644 man/selectDataUI.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 5ec5005e..c7ca4b1e 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -12,3 +12,4 @@ ^LICENSE\.md$ ^deploy\.sh$ ^\..*$ +^\.Rproj\.user$ diff --git a/DESCRIPTION b/DESCRIPTION index b001e57c..92a3b728 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: MpiIsoApp Title: Shiny App for Isotopes data base -Version: 22.06.2 +Version: 22.06.3 Author: INWT Statistics GmbH Maintainer: INWT Description: Shiny App contains: a data explorer tab, an interactive map and a static map, which should present model results. diff --git a/R/02-importData.R b/R/02-importData.R index e416a633..4e1d9274 100644 --- a/R/02-importData.R +++ b/R/02-importData.R @@ -236,8 +236,27 @@ importDataDialog <- function(ns){ title = "Import Data", footer = tagList( actionButton(ns("cancel"), "Cancel"), + actionButton(ns("addData"), "Add data"), actionButton(ns("accept"), "Accept") ), + tabsetPanel( + tabPanel( + "Select Data", + selectDataUI(ns = ns) + ), + tabPanel( + "Merge Data", + mergeDataUI(ns("dataMerger")) + ) + ) + ) +} + +#' Select Data UI +#' +#' @param ns namespace +selectDataUI <- function(ns){ + tagList( selectInput(ns("source"), "Source", choices = c("Pandora Platform" = "ckan","File" = "file", "URL" = "url")), conditionalPanel( condition = "input.source == 'ckan'", diff --git a/R/02-module-mergeData.R b/R/02-module-mergeData.R new file mode 100644 index 00000000..ccacb25b --- /dev/null +++ b/R/02-module-mergeData.R @@ -0,0 +1,25 @@ +#' Merge Data UI +#' +#' UI of the merge data module +#' +#' @param id id of module +mergeDataUI <- function(id) { + ns <- NS(id) + + tagList( + selectInput(ns("mergeList"), "Select Datasets", choices = NULL), + verbatimTextOutput(ns("preview")) + ) +} + +#' Merge Data Server +#' +#' Server function of the merge data module +mergeDataServer <- function(id) { + moduleServer( + id, + function(input, output, session) { + output$preview <- renderText({input$mergeList}) + } + ) +} diff --git a/man/mergeDataServer.Rd b/man/mergeDataServer.Rd new file mode 100644 index 00000000..f48d7d98 --- /dev/null +++ b/man/mergeDataServer.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-module-mergeData.R +\name{mergeDataServer} +\alias{mergeDataServer} +\title{Merge Data Server} +\usage{ +mergeDataServer(id) +} +\description{ +Server function of the merge data module +} diff --git a/man/mergeDataUI.Rd b/man/mergeDataUI.Rd new file mode 100644 index 00000000..c819ecbf --- /dev/null +++ b/man/mergeDataUI.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-module-mergeData.R +\name{mergeDataUI} +\alias{mergeDataUI} +\title{Merge Data UI} +\usage{ +mergeDataUI(id) +} +\arguments{ +\item{id}{id of module} +} +\description{ +UI of the merge data module +} diff --git a/man/selectDataUI.Rd b/man/selectDataUI.Rd new file mode 100644 index 00000000..0283e01a --- /dev/null +++ b/man/selectDataUI.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{selectDataUI} +\alias{selectDataUI} +\title{Select Data UI} +\usage{ +selectDataUI(ns) +} +\arguments{ +\item{ns}{namespace} +} +\description{ +Select Data UI +} From aab17525d1151a7e21fb3c897b8dfdd60aa78150 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 17 Jun 2022 15:25:21 +0200 Subject: [PATCH 2/8] extract function loadDataWrapper, new feature to load only head of data, prepare mergeData module --- R/02-importData.R | 202 +++++++++++++++--------- R/02-module-mergeData.R | 1 + man/getNrow.Rd | 18 +++ man/loadDataWrapper.Rd | 48 ++++++ tests/testthat/test-module-importData.R | 19 ++- 5 files changed, 213 insertions(+), 75 deletions(-) create mode 100644 man/getNrow.Rd create mode 100644 man/loadDataWrapper.Rd diff --git a/R/02-importData.R b/R/02-importData.R index 4e1d9274..f334ae5d 100644 --- a/R/02-importData.R +++ b/R/02-importData.R @@ -47,6 +47,7 @@ importDataServer <- function(id, dataSource <- reactiveVal(NULL) + # select source server ---- observeEvent(input$openPopup, ignoreNULL = TRUE, { reset("file") values$warnings <- list() @@ -115,6 +116,7 @@ importDataServer <- function(id, dataSource(list(file = tmp, filename = input$url)) }) + # specify file server ---- observeEvent(list( dataSource(), input$type, @@ -126,73 +128,24 @@ importDataServer <- function(id, { req(dataSource()) + # reset values values$dataImport <- NULL - - filepath <- dataSource()$file - filename <- dataSource()$filename values$warnings <- list() values$errors <- list() values$fileImportSuccess <- NULL - df <- tryCatch( - loadData( - filepath, - input$type, - input$colSep, - input$decSep, - isTRUE(input$rownames) - ), - error = function(e) { - values$warnings <- c(values$warnings, "Could not read in file.") - shinyjs::disable("accept") - NULL - }, - warning = function(w) { - values$warnings <- c(values$warnings, "Could not read in file.") - shinyjs::disable("accept") - NULL - } - ) - - if (is.null(df)) { - values$headData <- NULL - return(NULL) - } - - #attr(df, "includeSd") <- isTRUE(input$includeSd) - - ## set colnames - if (!is.null(colNames)) { - colnames(df) <- rep("", ncol(df)) - mini <- min(length(colNames()), ncol(df)) - colnames(df)[seq_len(mini)] <- colNames()[seq_len(mini)] - } - ## Import technically successful - values$fileName <- filename - values$dataImport <- as.data.frame(df) - - values$headData <- lapply(head(as.data.frame(df)), function(z) { - if (is.character(z)) { - substr(z, 1, 50) - } else { - z - } - })[1:min(ncol(df), 5)] - - ## Import valid? - lapply(customWarningChecks, function(fun) { - res <- fun()(df) - if (!isTRUE(res)) { - values$warnings <- c(values$warnings, res) - } - }) - - lapply(customErrorChecks, function(fun) { - res <- fun()(df) - if (!isTRUE(res)) { - values$errors <- c(values$errors, res) - } - }) + values <- loadDataWrapper( + values = values, + filepath = dataSource()$file, + filename = dataSource()$filename, + colNames = colNames, + type = input$type, + sep = input$colSep, + dec = input$decSep, + withRownames = isTRUE(input$rownames), + headOnly = FALSE, + customWarningChecks = customWarningChecks, + customErrorChecks = customErrorChecks) if (length(values$errors) > 0) { shinyjs::disable("accept") @@ -257,6 +210,7 @@ importDataDialog <- function(ns){ #' @param ns namespace selectDataUI <- function(ns){ tagList( + # select source UI ---- selectInput(ns("source"), "Source", choices = c("Pandora Platform" = "ckan","File" = "file", "URL" = "url")), conditionalPanel( condition = "input.source == 'ckan'", @@ -275,6 +229,8 @@ selectDataUI <- function(ns){ ns = ns, textInput(ns("url"), "URL") ), + tags$hr(), + # specify file UI ---- selectInput( ns("type"), "File type", @@ -300,12 +256,94 @@ selectDataUI <- function(ns){ ) } +#' Load Data Wrapper +#' +#' @inheritParams importDataServer +#' @param values (list) list with import specifications +#' @param filepath url or path +#' @param filename url or file name +#' @param type (character) file type input +#' @param sep (character) column separator input +#' @param dec (character) decimal separator input +#' @param withRownames (logical) contains rownames input +#' @param headOnly (logical) load only head (first n rows) of file +loadDataWrapper <- function(values, filepath, filename, colNames, + type, sep, dec, withRownames, headOnly, + customWarningChecks, customErrorChecks) { + df <- tryCatch( + loadData( + file = filepath, + type = type, + sep = sep, + dec = dec, + rownames = withRownames, + headOnly = headOnly + ), + error = function(e) { + values$warnings <- c(values$warnings, "Could not read in file.") + shinyjs::disable("accept") + NULL + }, + warning = function(w) { + values$warnings <- c(values$warnings, "Could not read in file.") + shinyjs::disable("accept") + NULL + } + ) + + if (is.null(df)) { + values$headData <- NULL + return(NULL) + } + + #attr(df, "includeSd") <- isTRUE(input$includeSd) + + ## set colnames + if (!is.null(colNames)) { + colnames(df) <- rep("", ncol(df)) + mini <- min(length(colNames()), ncol(df)) + colnames(df)[seq_len(mini)] <- colNames()[seq_len(mini)] + } + + ## Import technically successful + values$fileName <- filename + values$dataImport <- as.data.frame(df) + + ## create preview data + values$headData <- lapply(head(as.data.frame(df)), function(z) { + if (is.character(z)) { + substr(z, 1, 50) + } else { + z + } + })[1:min(ncol(df), 5)] + + ## Import valid? + lapply(customWarningChecks, function(fun) { + res <- fun()(df) + if (!isTRUE(res)) { + values$warnings <- c(values$warnings, res) + } + }) + + lapply(customErrorChecks, function(fun) { + res <- fun()(df) + if (!isTRUE(res)) { + values$errors <- c(values$errors, res) + } + }) + + values +} + + loadData <- function(file, type, sep = ",", dec = ".", - rownames = FALSE) { + rownames = FALSE, + headOnly = FALSE) { # if(type == "csv" | type == "txt"){ # codepages <- setNames(iconvlist(), iconvlist()) # x <- lapply(codepages, function(enc) try(suppressWarnings({read.csv(file, @@ -339,7 +377,8 @@ loadData <- dec = dec, stringsAsFactors = FALSE, row.names = NULL, - fileEncoding = encTry + fileEncoding = encTry, + nrows = getNrow(headOnly, type) ) }), txt = suppressWarnings({ @@ -349,27 +388,27 @@ loadData <- dec = dec, stringsAsFactors = FALSE, row.names = NULL, - fileEncoding = encTry + fileEncoding = encTry, + nrows = getNrow(headOnly, type) ) }), - xlsx = read.xlsx(file), + xlsx = read.xlsx(file, rows = getNrow(headOnly, type)), xls = suppressWarnings({ - readxl::read_excel(file) + readxl::read_excel(file, n_max = getNrow(headOnly, type)) }), - ods = readODS::read_ods(file) + ods = readODS::read_ods(file, range = getNrow(headOnly, type)) ) if (is.null(data)) return(NULL) - - if (any(dim(data) == 1)) { - warning("Number of rows or columns equal to 1") + if (is.null(dim(data))) { + stop("Could not determine dimensions of data") return(NULL) } - if (is.null(dim(data))) { - stop("Could not determine dimensions of data") + if (any(dim(data) == 1)) { + warning("Number of rows or columns equal to 1") return(NULL) } @@ -387,3 +426,20 @@ loadData <- return(data) } + +#' get nRow +#' +#' @param headOnly (logical) if TRUE, set maximal number of rows to n +#' @param type (character) file type +#' @param n (numeric) maximal number of rows if headOnly +getNrow <- function(headOnly, type, n = 3) { + if (headOnly) { + if (type == "xlsx") return(1:n) else + if (type == "ods") return(paste0("A1:C", n)) else + return(n) + } else { + if (type %in% c("xlsx", "ods")) return(NULL) else + if (type == "xls") return(Inf) else + return(-999) + } +} diff --git a/R/02-module-mergeData.R b/R/02-module-mergeData.R index ccacb25b..86030051 100644 --- a/R/02-module-mergeData.R +++ b/R/02-module-mergeData.R @@ -15,6 +15,7 @@ mergeDataUI <- function(id) { #' Merge Data Server #' #' Server function of the merge data module +#' @param id id of module mergeDataServer <- function(id) { moduleServer( id, diff --git a/man/getNrow.Rd b/man/getNrow.Rd new file mode 100644 index 00000000..e8c8f3e3 --- /dev/null +++ b/man/getNrow.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{getNrow} +\alias{getNrow} +\title{get nRow} +\usage{ +getNrow(headOnly, type, n = 3) +} +\arguments{ +\item{headOnly}{(logical) if TRUE, set maximal number of rows to n} + +\item{type}{(character) file type} + +\item{n}{(numeric) maximal number of rows if headOnly} +} +\description{ +get nRow +} diff --git a/man/loadDataWrapper.Rd b/man/loadDataWrapper.Rd new file mode 100644 index 00000000..0a07d854 --- /dev/null +++ b/man/loadDataWrapper.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{loadDataWrapper} +\alias{loadDataWrapper} +\title{Load Data Wrapper} +\usage{ +loadDataWrapper( + values, + filepath, + filename, + colNames, + type, + sep, + dec, + withRownames, + headOnly, + customWarningChecks, + customErrorChecks +) +} +\arguments{ +\item{filepath}{url or path} + +\item{filename}{url or file name} + +\item{colNames}{(reactive) use this for colnames of imported data} + +\item{type}{(character) file type input} + +\item{sep}{(character) column separator input} + +\item{dec}{(character) decimal separator input} + +\item{withRownames}{(logical) contains rownames input} + +\item{headOnly}{(logical) load only head (first n rows) of file} + +\item{customWarningChecks}{list of reactive functions which will be executed after importing of data. +functions need to return TRUE if check is successful or a character with a warning otherwise.} + +\item{customErrorChecks}{list of reactive functions which will be executed after importing of data. +functions need to return TRUE if check is successful or a character with a warning otherwise.} + +\item{value}{(list) list with import specifications} +} +\description{ +Load Data Wrapper +} diff --git a/tests/testthat/test-module-importData.R b/tests/testthat/test-module-importData.R index 965ab838..bfa6139f 100644 --- a/tests/testthat/test-module-importData.R +++ b/tests/testthat/test-module-importData.R @@ -22,7 +22,6 @@ test_that("Test module importData", { rownames = FALSE, accept = TRUE ) - expect_equal( names(session$returned()), "https://pandoradata.earth/dataset/cbbc35e0-af60-4224-beea-181be10f7f71/resource/f7581eb1-b2b8-4926-ba77-8bc92ddb4fdb/download/cima-humans.xlsx" @@ -30,6 +29,22 @@ test_that("Test module importData", { expect_true(all( c("Entry.ID", "Reference", "Link", "DOI") %in% names(session$returned()[[1]]) )) - expect_true(nrow(session$returned()[[1]]) > 0) + expect_true(nrow(session$returned()[[1]]) > 100) + + expect_equal( + colnames(session$returned()[[1]])[1:10], + c( + "Entry.ID", + "Submitter.ID", + "Context.ID", + "Individual.ID", + "Sample.ID", + "Sex", + "Age.Category", + "Min..Age.(yrs)", + "Max..Age.(yrs)", + "Sampled.Element" + ) + ) }) }) From a9abc5565fc8d5dc96774dbbb4879777d0f610df Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Mon, 20 Jun 2022 10:26:58 +0200 Subject: [PATCH 3/8] separate between data loading for preview and data loading for import, update import UI, autoformat --- R/02-importData.R | 219 ++++++++++++++++++++++++++--------------- R/03-dataExplorer.R | 2 +- man/getNrow.Rd | 2 +- man/loadDataWrapper.Rd | 4 +- man/mergeDataServer.Rd | 3 + 5 files changed, 148 insertions(+), 82 deletions(-) diff --git a/R/02-importData.R b/R/02-importData.R index f334ae5d..2d0fa1c7 100644 --- a/R/02-importData.R +++ b/R/02-importData.R @@ -60,7 +60,8 @@ importDataServer <- function(id, showModal(importDataDialog(ns = ns)) - titles <- unlist(lapply(ckanFiles(), `[[`, "title")) + titles <- + unlist(lapply(ckanFiles(), `[[`, "title")) updateSelectInput(session, "ckanRecord", choices = titles) }) @@ -73,9 +74,10 @@ importDataServer <- function(id, req(ckanRecord()) resources <- names(ckanRecord()$resources) - labels <- unlist(lapply(ckanRecord()$resources, function(x) { - paste(x$name, " (", x$format, ")") - })) + labels <- + unlist(lapply(ckanRecord()$resources, function(x) { + paste(x$name, " (", x$format, ")") + })) setNames(resources, labels) }) @@ -86,7 +88,8 @@ importDataServer <- function(id, observe({ req(input$source == "ckan") - resource <- ckanRecord()$resources[[input$ckanResource]] + resource <- + ckanRecord()$resources[[input$ckanResource]] req(resource) dataSource(list(file = resource$url, filename = resource$url)) }) @@ -107,7 +110,8 @@ importDataServer <- function(id, tmp <- tempfile() - res <- try(download.file(input$url, destfile = tmp)) + res <- + try(download.file(input$url, destfile = tmp)) if (inherits(res, "try-error")) { alert("Could not load remote file") return() @@ -134,32 +138,42 @@ importDataServer <- function(id, values$errors <- list() values$fileImportSuccess <- NULL - values <- loadDataWrapper( - values = values, - filepath = dataSource()$file, - filename = dataSource()$filename, - colNames = colNames, - type = input$type, - sep = input$colSep, - dec = input$decSep, - withRownames = isTRUE(input$rownames), - headOnly = FALSE, - customWarningChecks = customWarningChecks, - customErrorChecks = customErrorChecks) + withProgress({ + # load first lines only + values <- loadDataWrapper( + values = values, + filepath = dataSource()$file, + filename = dataSource()$filename, + colNames = colNames, + type = input$type, + sep = input$colSep, + dec = input$decSep, + withRownames = isTRUE(input$rownames), + headOnly = TRUE, + customWarningChecks = customWarningChecks, + customErrorChecks = customErrorChecks + ) + }, + value = 0.75, + message = 'load preview data ...') if (length(values$errors) > 0) { shinyjs::disable("accept") return(NULL) + } else { + shinyjs::enable("accept") + values$fileImportSuccess <- + "Data import was successful" } - shinyjs::enable("accept") - values$fileImportSuccess <- "Data import was successful" }) output$warning <- renderUI(tagList(lapply(values$warnings, tags$p))) - output$error <- renderUI(tagList(lapply(values$errors, tags$p))) - output$success <- renderText(values$fileImportSuccess) + output$error <- + renderUI(tagList(lapply(values$errors, tags$p))) + output$success <- + renderText(values$fileImportSuccess) output$preview <- renderTable( values$headData, @@ -173,9 +187,29 @@ importDataServer <- function(id, }) observeEvent(input$accept, { + withProgress({ + # load full data set + values <- loadDataWrapper( + values = values, + filepath = dataSource()$file, + filename = dataSource()$filename, + colNames = colNames, + type = input$type, + sep = input$colSep, + dec = input$decSep, + withRownames = isTRUE(input$rownames), + headOnly = FALSE, + customWarningChecks = customWarningChecks, + customErrorChecks = customErrorChecks + ) + }, + value = 0.75, + message = 'import full data ...') + removeModal() - values$data[[values$fileName]] <- values$dataImport + values$data[[values$fileName]] <- + values$dataImport }) reactive(values$data) @@ -183,24 +217,20 @@ importDataServer <- function(id, } # import data dialog ui -importDataDialog <- function(ns){ +importDataDialog <- function(ns) { modalDialog( useShinyjs(), title = "Import Data", footer = tagList( - actionButton(ns("cancel"), "Cancel"), actionButton(ns("addData"), "Add data"), - actionButton(ns("accept"), "Accept") + actionButton(ns("accept"), "Accept"), + actionButton(ns("cancel"), "Cancel") ), tabsetPanel( - tabPanel( - "Select Data", - selectDataUI(ns = ns) - ), - tabPanel( - "Merge Data", - mergeDataUI(ns("dataMerger")) - ) + tabPanel("Select Data", + selectDataUI(ns = ns)), + tabPanel("Merge Data", + mergeDataUI(ns("dataMerger"))) ) ) } @@ -208,47 +238,64 @@ importDataDialog <- function(ns){ #' Select Data UI #' #' @param ns namespace -selectDataUI <- function(ns){ +selectDataUI <- function(ns) { tagList( - # select source UI ---- - selectInput(ns("source"), "Source", choices = c("Pandora Platform" = "ckan","File" = "file", "URL" = "url")), - conditionalPanel( - condition = "input.source == 'ckan'", - ns = ns, - selectInput(ns("ckanRecord"), "Pandora dataset", choices = NULL), - selectizeInput(ns("ckanResource"), "Pandora dataset resource", choices = NULL + fluidRow( + column(4, + # select source UI ---- + selectInput( + ns("source"), + "Source", + choices = c( + "Pandora Platform" = "ckan", + "File" = "file", + "URL" = "url" + ) + )), + column( + 8, + conditionalPanel( + condition = "input.source == 'ckan'", + ns = ns, + selectInput(ns("ckanRecord"), "Pandora dataset", choices = NULL), + selectizeInput(ns("ckanResource"), "Pandora dataset resource", choices = NULL) + ), + conditionalPanel(condition = "input.source == 'file'", + ns = ns, + fileInput(ns("file"), "File")), + conditionalPanel(condition = "input.source == 'url'", + ns = ns, + textInput(ns("url"), "URL")) ) ), - conditionalPanel( - condition = "input.source == 'file'", - ns = ns, - fileInput(ns("file"), "File") - ), - conditionalPanel( - condition = "input.source == 'url'", - ns = ns, - textInput(ns("url"), "URL") - ), tags$hr(), # specify file UI ---- - selectInput( - ns("type"), - "File type", - choices = c("xls(x)" = "xlsx", "csv", "ods", "txt"), - selected = "xlsx" - ), - conditionalPanel( - condition = paste0("input.type == 'csv' || input.type == 'txt'"), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput(ns("colSep"), "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput(ns("decSep"), "decimal separator:", value = ".")), - ns = ns + fluidRow(column( + 4, + selectInput( + ns("type"), + "File type", + choices = c("xls(x)" = "xlsx", "csv", "ods", "txt"), + selected = "xlsx" + ) ), + column( + 8, + conditionalPanel( + condition = paste0("input.type == 'csv' || input.type == 'txt'"), + div(style = "display: inline-block;horizontal-align:top; width: 80px;", + textInput( + ns("colSep"), "column separator:", value = "," + )), + div(style = "display: inline-block;horizontal-align:top; width: 80px;", + textInput( + ns("decSep"), "decimal separator:", value = "." + )), + ns = ns + ) + )), checkboxInput(ns("rownames"), "First column contains rownames"), - helpText( - "The first row in your file need to contain variable names." - ), + helpText("The first row in your file need to contain variable names."), div(class = "text-danger", uiOutput(ns("warning"))), div(class = "text-danger", uiOutput(ns("error"))), div(class = "text-success", textOutput(ns("success"))), @@ -267,9 +314,17 @@ selectDataUI <- function(ns){ #' @param dec (character) decimal separator input #' @param withRownames (logical) contains rownames input #' @param headOnly (logical) load only head (first n rows) of file -loadDataWrapper <- function(values, filepath, filename, colNames, - type, sep, dec, withRownames, headOnly, - customWarningChecks, customErrorChecks) { +loadDataWrapper <- function(values, + filepath, + filename, + colNames, + type, + sep, + dec, + withRownames, + headOnly, + customWarningChecks, + customErrorChecks) { df <- tryCatch( loadData( file = filepath, @@ -432,14 +487,22 @@ loadData <- #' @param headOnly (logical) if TRUE, set maximal number of rows to n #' @param type (character) file type #' @param n (numeric) maximal number of rows if headOnly -getNrow <- function(headOnly, type, n = 3) { +getNrow <- function(headOnly, type, n = 4) { if (headOnly) { - if (type == "xlsx") return(1:n) else - if (type == "ods") return(paste0("A1:C", n)) else - return(n) + if (type == "xlsx") + return(1:n) + else + if (type == "ods") + return(paste0("A1:C", n)) + else + return(n) } else { - if (type %in% c("xlsx", "ods")) return(NULL) else - if (type == "xls") return(Inf) else - return(-999) + if (type %in% c("xlsx", "ods")) + return(NULL) + else + if (type == "xls") + return(Inf) + else + return(-999) } } diff --git a/R/03-dataExplorer.R b/R/03-dataExplorer.R index 935512a6..27657ad6 100644 --- a/R/03-dataExplorer.R +++ b/R/03-dataExplorer.R @@ -217,7 +217,7 @@ dataExplorerServer <- function(id) { isoDataRaw(d) }, value = 0.75, - message = 'Get remote data ...' + message = 'Get remote data from database ...' ) }) diff --git a/man/getNrow.Rd b/man/getNrow.Rd index e8c8f3e3..e618964c 100644 --- a/man/getNrow.Rd +++ b/man/getNrow.Rd @@ -4,7 +4,7 @@ \alias{getNrow} \title{get nRow} \usage{ -getNrow(headOnly, type, n = 3) +getNrow(headOnly, type, n = 4) } \arguments{ \item{headOnly}{(logical) if TRUE, set maximal number of rows to n} diff --git a/man/loadDataWrapper.Rd b/man/loadDataWrapper.Rd index 0a07d854..267563d9 100644 --- a/man/loadDataWrapper.Rd +++ b/man/loadDataWrapper.Rd @@ -19,6 +19,8 @@ loadDataWrapper( ) } \arguments{ +\item{values}{(list) list with import specifications} + \item{filepath}{url or path} \item{filename}{url or file name} @@ -40,8 +42,6 @@ functions need to return TRUE if check is successful or a character with a warni \item{customErrorChecks}{list of reactive functions which will be executed after importing of data. functions need to return TRUE if check is successful or a character with a warning otherwise.} - -\item{value}{(list) list with import specifications} } \description{ Load Data Wrapper diff --git a/man/mergeDataServer.Rd b/man/mergeDataServer.Rd index f48d7d98..daade2a2 100644 --- a/man/mergeDataServer.Rd +++ b/man/mergeDataServer.Rd @@ -6,6 +6,9 @@ \usage{ mergeDataServer(id) } +\arguments{ +\item{id}{id of module} +} \description{ Server function of the merge data module } From d60642e51884332707ef11e79d0566f7d00fa1c3 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Mon, 20 Jun 2022 14:06:25 +0200 Subject: [PATCH 4/8] use datatable in preview, hide Merge Data option, remove headData for preview, use dataImport instead, cut too long strings in preview --- NAMESPACE | 3 +- R/00-Namespace.R | 4 +- R/02-importData.R | 127 +++++++++++++--------- man/cutAllLongStrings.Rd | 16 +++ man/{selectDataUI.Rd => selectDataTab.Rd} | 6 +- tests/testthat/test-module-importData.R | 121 +++++++++++++++++++++ 6 files changed, 222 insertions(+), 55 deletions(-) create mode 100644 man/cutAllLongStrings.Rd rename man/{selectDataUI.Rd => selectDataTab.Rd} (75%) diff --git a/NAMESPACE b/NAMESPACE index 03aca3d3..a810419a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -50,7 +50,8 @@ export(savedMapsTab) export(savedMapsTabUI) export(setSkin) export(startApplication) -import(shiny, except = renderDataTable) +import(shiny, except = c(renderDataTable, dataTableOutput)) +importFrom(DT,dataTableOutput) importFrom(DT,datatable) importFrom(DT,renderDataTable) importFrom(MASS,kde2d) diff --git a/R/00-Namespace.R b/R/00-Namespace.R index 1f8ea3f6..4164e757 100644 --- a/R/00-Namespace.R +++ b/R/00-Namespace.R @@ -1,9 +1,9 @@ -#' @rawNamespace import(shiny, except = renderDataTable) +#' @rawNamespace import(shiny, except = c(renderDataTable, dataTableOutput)) #' @importFrom animation saveGIF #' @importFrom coda raftery.diag gelman.diag geweke.diag heidel.diag mcmc #' @importFrom colourpicker colourInput #' @importFrom rcarbon calibrate sampleDates -#' @importFrom DT datatable renderDataTable +#' @importFrom DT datatable renderDataTable dataTableOutput #' @importFrom geometry convhulln inhulln #' @importFrom ggplot2 ggplot theme theme_light coord_cartesian geom_point theme_light theme labs #' geom_errorbar aes_ element_blank element_text position_dodge aes geom_boxplot xlab diff --git a/R/02-importData.R b/R/02-importData.R index 2d0fa1c7..400f3f72 100644 --- a/R/02-importData.R +++ b/R/02-importData.R @@ -157,15 +157,14 @@ importDataServer <- function(id, value = 0.75, message = 'load preview data ...') - if (length(values$errors) > 0) { - shinyjs::disable("accept") - return(NULL) + if (length(values$errors) > 0 || + length(values$warnings) > 0) { + shinyjs::disable(ns("accept"), asis = TRUE) } else { - shinyjs::enable("accept") + shinyjs::enable(ns("accept"), asis = TRUE) values$fileImportSuccess <- "Data import was successful" } - }) output$warning <- @@ -175,18 +174,31 @@ importDataServer <- function(id, output$success <- renderText(values$fileImportSuccess) - output$preview <- renderTable( - values$headData, - bordered = TRUE, - rownames = FALSE, - colnames = TRUE - ) + output$preview <- renderDataTable({ + req(values$dataImport) + + previewData <- + cutAllLongStrings(values$dataImport, cutAt = 20) + DT::datatable( + previewData, + filter = "none", + selection = "none", + rownames = FALSE, + options = list( + dom = "t", + ordering = FALSE, + scrollX = TRUE + ) + ) + }) observeEvent(input$cancel, { removeModal() }) observeEvent(input$accept, { + removeModal() + withProgress({ # load full data set values <- loadDataWrapper( @@ -206,8 +218,6 @@ importDataServer <- function(id, value = 0.75, message = 'import full data ...') - removeModal() - values$data[[values$fileName]] <- values$dataImport }) @@ -219,18 +229,16 @@ importDataServer <- function(id, # import data dialog ui importDataDialog <- function(ns) { modalDialog( - useShinyjs(), + shinyjs::useShinyjs(), title = "Import Data", footer = tagList( - actionButton(ns("addData"), "Add data"), + #actionButton(ns("addData"), "Add data"), actionButton(ns("accept"), "Accept"), - actionButton(ns("cancel"), "Cancel") - ), - tabsetPanel( - tabPanel("Select Data", - selectDataUI(ns = ns)), - tabPanel("Merge Data", - mergeDataUI(ns("dataMerger"))) + actionButton(ns("cancel"), "Cancel")), + tabsetPanel(tabPanel("Select Data", + selectDataTab(ns = ns))#, + # tabPanel("Merge Data", + # mergeDataUI(ns("dataMerger"))) ) ) } @@ -238,8 +246,9 @@ importDataDialog <- function(ns) { #' Select Data UI #' #' @param ns namespace -selectDataUI <- function(ns) { +selectDataTab <- function(ns) { tagList( + tags$br(), fluidRow( column(4, # select source UI ---- @@ -283,14 +292,14 @@ selectDataUI <- function(ns) { 8, conditionalPanel( condition = paste0("input.type == 'csv' || input.type == 'txt'"), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput( - ns("colSep"), "column separator:", value = "," - )), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput( - ns("decSep"), "decimal separator:", value = "." - )), + fluidRow(column( + width = 5, + textInput(ns("colSep"), "column separator:", value = ",") + ), + column( + width = 5, + textInput(ns("decSep"), "decimal separator:", value = ".") + )), ns = ns ) )), @@ -299,7 +308,12 @@ selectDataUI <- function(ns) { div(class = "text-danger", uiOutput(ns("warning"))), div(class = "text-danger", uiOutput(ns("error"))), div(class = "text-success", textOutput(ns("success"))), - tableOutput(ns("preview")) + tags$br(), + tags$h5("Preview:"), + fluidRow(column(12, + dataTableOutput(ns( + "preview" + )))) ) } @@ -335,22 +349,15 @@ loadDataWrapper <- function(values, headOnly = headOnly ), error = function(e) { - values$warnings <- c(values$warnings, "Could not read in file.") - shinyjs::disable("accept") + values$errors <- c(values$errors, "Could not read in file.") NULL }, warning = function(w) { values$warnings <- c(values$warnings, "Could not read in file.") - shinyjs::disable("accept") NULL } ) - if (is.null(df)) { - values$headData <- NULL - return(NULL) - } - #attr(df, "includeSd") <- isTRUE(input$includeSd) ## set colnames @@ -364,15 +371,6 @@ loadDataWrapper <- function(values, values$fileName <- filename values$dataImport <- as.data.frame(df) - ## create preview data - values$headData <- lapply(head(as.data.frame(df)), function(z) { - if (is.character(z)) { - substr(z, 1, 50) - } else { - z - } - })[1:min(ncol(df), 5)] - ## Import valid? lapply(customWarningChecks, function(fun) { res <- fun()(df) @@ -482,6 +480,37 @@ loadData <- return(data) } + +#' Cut All Strings +#' +#' @param df (data.frame) data.frame with character and non-character columns +#' @param cutAt (numeric) number of characters after which to cut the entries of an character-column +cutAllLongStrings <- function(df, cutAt = 50) { + cutStrings <- function(vec, cutAt) { + if (any(nchar(vec) > cutAt, na.rm = TRUE)) { + index <- !is.na(vec) & nchar(vec) > cutAt + vec[index] <- paste0(substr(vec[index], 1, cutAt), "...") + } + + vec + } + + df <- lapply(df, function(z) { + if (!is.character(z)) + return(z) + + cutStrings(z, cutAt = cutAt) + }) %>% + as.data.frame() + + dfColNames <- colnames(df) %>% + cutStrings(cutAt = max(10, (cutAt - 3))) + colnames(df) <- dfColNames + + df +} + + #' get nRow #' #' @param headOnly (logical) if TRUE, set maximal number of rows to n diff --git a/man/cutAllLongStrings.Rd b/man/cutAllLongStrings.Rd new file mode 100644 index 00000000..2416d435 --- /dev/null +++ b/man/cutAllLongStrings.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{cutAllLongStrings} +\alias{cutAllLongStrings} +\title{Cut All Strings} +\usage{ +cutAllLongStrings(df, cutAt = 50) +} +\arguments{ +\item{df}{(data.frame) data.frame with character and non-character columns} + +\item{cutAt}{(numeric) number of characters after which to cut the entries of an character-column} +} +\description{ +Cut All Strings +} diff --git a/man/selectDataUI.Rd b/man/selectDataTab.Rd similarity index 75% rename from man/selectDataUI.Rd rename to man/selectDataTab.Rd index 0283e01a..4313c978 100644 --- a/man/selectDataUI.Rd +++ b/man/selectDataTab.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/02-importData.R -\name{selectDataUI} -\alias{selectDataUI} +\name{selectDataTab} +\alias{selectDataTab} \title{Select Data UI} \usage{ -selectDataUI(ns) +selectDataTab(ns) } \arguments{ \item{ns}{namespace} diff --git a/tests/testthat/test-module-importData.R b/tests/testthat/test-module-importData.R index bfa6139f..279ceab2 100644 --- a/tests/testthat/test-module-importData.R +++ b/tests/testthat/test-module-importData.R @@ -48,3 +48,124 @@ test_that("Test module importData", { ) }) }) + +test_that("cutAllLongStrings function", { + testData <- + structure( + list( + Entry.ID = c(1, 2, 3, 4, 5, 6), + Context.ID = c( + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_ + ), + Individual.ID = c( + "Høre kranie", + "Ringebu 3A", + "Bergen", + "Uvdal", + "Ringebu 3B", + "102" + ), + Sample.ID = c(NA, NA, NA, + NA, NA, "VHM 24"), + Sex = c(NA, NA, NA, NA, NA, "M"), + Latitude = c( + 61.153097, + 61.527761, + 60.393642, + 60.273504, + 61.527761, + 58.385741 + ), + Longitude = c(8.80468, 10.14467, 5.319837, + 8.243344, 10.14467, 13.646216), + Reference = c( + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åborg, D.C. (2013). Hierarchy through Diet. Stable isotope analysis of male graves of the estate church graveyard in Varnhem. Unpublished BA dissertation: Stockholm University." + ), + Link = c( + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "http://www.diva-portal.org/smash/record.jsf?pid=diva2%3A622264&dswid=-9506" + ), + Publication.Year = c(1998, + 1998, 1998, 1998, 1998, 2013), + IRMS.Lab.Institution.Stable.Sulphur.Measurement = c(NA, + NA, NA, NA, NA, "Stockholm University") + ), + row.names = c(NA, 6L), + class = "data.frame" + ) + + expect_equal( + cutAllLongStrings(testData, cutAt = 30), + structure( + list( + Entry.ID = c(1, 2, 3, 4, 5, 6), + Context.ID = c( + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_ + ), + Individual.ID = c( + "Høre kranie", + "Ringebu 3A", + "Bergen", + "Uvdal", + "Ringebu 3B", + "102" + ), + Sample.ID = c(NA, NA, NA, NA, + NA, "VHM 24"), + Sex = c(NA, NA, NA, NA, NA, "M"), + Latitude = c( + 61.153097, + 61.527761, + 60.393642, + 60.273504, + 61.527761, + 58.385741 + ), + Longitude = c(8.80468, + 10.14467, 5.319837, 8.243344, 10.14467, 13.646216), + Reference = c( + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åborg, D.C. (2013). Hierarchy ..." + ), + Link = c( + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "http://www.diva-portal.org/sma..." + ), + Publication.Year = c(1998, + 1998, 1998, 1998, 1998, 2013), + IRMS.Lab.Institution.Stable... = c(NA, + NA, NA, NA, NA, "Stockholm University") + ), + class = "data.frame", + row.names = c(NA, + -6L) + ) + ) +}) From ceac462a69f6fcefadad725e01027c2fa2c3819a Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 22 Jun 2022 13:42:53 +0200 Subject: [PATCH 5/8] Feat/37 Prepare: merge data UI (#39) * extract selectDataUI function, add new tabPanel "Merge Data", new module mergeData * extract function loadDataWrapper, new feature to load only head of data, prepare mergeData module * separate between data loading for preview and data loading for import, update import UI, autoformat * use datatable in preview, hide Merge Data option, remove headData for preview, use dataImport instead, cut too long strings in preview --- .Rbuildignore | 1 + DESCRIPTION | 2 +- NAMESPACE | 3 +- R/00-Namespace.R | 4 +- R/02-importData.R | 435 ++++++++++++++++-------- R/02-module-mergeData.R | 26 ++ R/03-dataExplorer.R | 2 +- man/cutAllLongStrings.Rd | 16 + man/getNrow.Rd | 18 + man/loadDataWrapper.Rd | 48 +++ man/mergeDataServer.Rd | 14 + man/mergeDataUI.Rd | 14 + man/selectDataTab.Rd | 14 + tests/testthat/test-module-importData.R | 140 +++++++- 14 files changed, 596 insertions(+), 141 deletions(-) create mode 100644 R/02-module-mergeData.R create mode 100644 man/cutAllLongStrings.Rd create mode 100644 man/getNrow.Rd create mode 100644 man/loadDataWrapper.Rd create mode 100644 man/mergeDataServer.Rd create mode 100644 man/mergeDataUI.Rd create mode 100644 man/selectDataTab.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 5ec5005e..c7ca4b1e 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -12,3 +12,4 @@ ^LICENSE\.md$ ^deploy\.sh$ ^\..*$ +^\.Rproj\.user$ diff --git a/DESCRIPTION b/DESCRIPTION index b001e57c..92a3b728 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: MpiIsoApp Title: Shiny App for Isotopes data base -Version: 22.06.2 +Version: 22.06.3 Author: INWT Statistics GmbH Maintainer: INWT Description: Shiny App contains: a data explorer tab, an interactive map and a static map, which should present model results. diff --git a/NAMESPACE b/NAMESPACE index 03aca3d3..a810419a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -50,7 +50,8 @@ export(savedMapsTab) export(savedMapsTabUI) export(setSkin) export(startApplication) -import(shiny, except = renderDataTable) +import(shiny, except = c(renderDataTable, dataTableOutput)) +importFrom(DT,dataTableOutput) importFrom(DT,datatable) importFrom(DT,renderDataTable) importFrom(MASS,kde2d) diff --git a/R/00-Namespace.R b/R/00-Namespace.R index 1f8ea3f6..4164e757 100644 --- a/R/00-Namespace.R +++ b/R/00-Namespace.R @@ -1,9 +1,9 @@ -#' @rawNamespace import(shiny, except = renderDataTable) +#' @rawNamespace import(shiny, except = c(renderDataTable, dataTableOutput)) #' @importFrom animation saveGIF #' @importFrom coda raftery.diag gelman.diag geweke.diag heidel.diag mcmc #' @importFrom colourpicker colourInput #' @importFrom rcarbon calibrate sampleDates -#' @importFrom DT datatable renderDataTable +#' @importFrom DT datatable renderDataTable dataTableOutput #' @importFrom geometry convhulln inhulln #' @importFrom ggplot2 ggplot theme theme_light coord_cartesian geom_point theme_light theme labs #' geom_errorbar aes_ element_blank element_text position_dodge aes geom_boxplot xlab diff --git a/R/02-importData.R b/R/02-importData.R index e416a633..400f3f72 100644 --- a/R/02-importData.R +++ b/R/02-importData.R @@ -47,6 +47,7 @@ importDataServer <- function(id, dataSource <- reactiveVal(NULL) + # select source server ---- observeEvent(input$openPopup, ignoreNULL = TRUE, { reset("file") values$warnings <- list() @@ -59,7 +60,8 @@ importDataServer <- function(id, showModal(importDataDialog(ns = ns)) - titles <- unlist(lapply(ckanFiles(), `[[`, "title")) + titles <- + unlist(lapply(ckanFiles(), `[[`, "title")) updateSelectInput(session, "ckanRecord", choices = titles) }) @@ -72,9 +74,10 @@ importDataServer <- function(id, req(ckanRecord()) resources <- names(ckanRecord()$resources) - labels <- unlist(lapply(ckanRecord()$resources, function(x) { - paste(x$name, " (", x$format, ")") - })) + labels <- + unlist(lapply(ckanRecord()$resources, function(x) { + paste(x$name, " (", x$format, ")") + })) setNames(resources, labels) }) @@ -85,7 +88,8 @@ importDataServer <- function(id, observe({ req(input$source == "ckan") - resource <- ckanRecord()$resources[[input$ckanResource]] + resource <- + ckanRecord()$resources[[input$ckanResource]] req(resource) dataSource(list(file = resource$url, filename = resource$url)) }) @@ -106,7 +110,8 @@ importDataServer <- function(id, tmp <- tempfile() - res <- try(download.file(input$url, destfile = tmp)) + res <- + try(download.file(input$url, destfile = tmp)) if (inherits(res, "try-error")) { alert("Could not load remote file") return() @@ -115,6 +120,7 @@ importDataServer <- function(id, dataSource(list(file = tmp, filename = input$url)) }) + # specify file server ---- observeEvent(list( dataSource(), input$type, @@ -126,94 +132,65 @@ importDataServer <- function(id, { req(dataSource()) + # reset values values$dataImport <- NULL - - filepath <- dataSource()$file - filename <- dataSource()$filename values$warnings <- list() values$errors <- list() values$fileImportSuccess <- NULL - df <- tryCatch( - loadData( - filepath, - input$type, - input$colSep, - input$decSep, - isTRUE(input$rownames) - ), - error = function(e) { - values$warnings <- c(values$warnings, "Could not read in file.") - shinyjs::disable("accept") - NULL - }, - warning = function(w) { - values$warnings <- c(values$warnings, "Could not read in file.") - shinyjs::disable("accept") - NULL - } - ) - - if (is.null(df)) { - values$headData <- NULL - return(NULL) - } - - #attr(df, "includeSd") <- isTRUE(input$includeSd) - ## set colnames - if (!is.null(colNames)) { - colnames(df) <- rep("", ncol(df)) - mini <- min(length(colNames()), ncol(df)) - colnames(df)[seq_len(mini)] <- colNames()[seq_len(mini)] + withProgress({ + # load first lines only + values <- loadDataWrapper( + values = values, + filepath = dataSource()$file, + filename = dataSource()$filename, + colNames = colNames, + type = input$type, + sep = input$colSep, + dec = input$decSep, + withRownames = isTRUE(input$rownames), + headOnly = TRUE, + customWarningChecks = customWarningChecks, + customErrorChecks = customErrorChecks + ) + }, + value = 0.75, + message = 'load preview data ...') + + if (length(values$errors) > 0 || + length(values$warnings) > 0) { + shinyjs::disable(ns("accept"), asis = TRUE) + } else { + shinyjs::enable(ns("accept"), asis = TRUE) + values$fileImportSuccess <- + "Data import was successful" } - - ## Import technically successful - values$fileName <- filename - values$dataImport <- as.data.frame(df) - - values$headData <- lapply(head(as.data.frame(df)), function(z) { - if (is.character(z)) { - substr(z, 1, 50) - } else { - z - } - })[1:min(ncol(df), 5)] - - ## Import valid? - lapply(customWarningChecks, function(fun) { - res <- fun()(df) - if (!isTRUE(res)) { - values$warnings <- c(values$warnings, res) - } - }) - - lapply(customErrorChecks, function(fun) { - res <- fun()(df) - if (!isTRUE(res)) { - values$errors <- c(values$errors, res) - } - }) - - if (length(values$errors) > 0) { - shinyjs::disable("accept") - return(NULL) - } - - shinyjs::enable("accept") - values$fileImportSuccess <- "Data import was successful" }) output$warning <- renderUI(tagList(lapply(values$warnings, tags$p))) - output$error <- renderUI(tagList(lapply(values$errors, tags$p))) - output$success <- renderText(values$fileImportSuccess) - - output$preview <- renderTable( - values$headData, - bordered = TRUE, - rownames = FALSE, - colnames = TRUE - ) + output$error <- + renderUI(tagList(lapply(values$errors, tags$p))) + output$success <- + renderText(values$fileImportSuccess) + + output$preview <- renderDataTable({ + req(values$dataImport) + + previewData <- + cutAllLongStrings(values$dataImport, cutAt = 20) + DT::datatable( + previewData, + filter = "none", + selection = "none", + rownames = FALSE, + options = list( + dom = "t", + ordering = FALSE, + scrollX = TRUE + ) + ) + }) observeEvent(input$cancel, { removeModal() @@ -222,7 +199,27 @@ importDataServer <- function(id, observeEvent(input$accept, { removeModal() - values$data[[values$fileName]] <- values$dataImport + withProgress({ + # load full data set + values <- loadDataWrapper( + values = values, + filepath = dataSource()$file, + filename = dataSource()$filename, + colNames = colNames, + type = input$type, + sep = input$colSep, + dec = input$decSep, + withRownames = isTRUE(input$rownames), + headOnly = FALSE, + customWarningChecks = customWarningChecks, + customErrorChecks = customErrorChecks + ) + }, + value = 0.75, + message = 'import full data ...') + + values$data[[values$fileName]] <- + values$dataImport }) reactive(values$data) @@ -230,63 +227,176 @@ importDataServer <- function(id, } # import data dialog ui -importDataDialog <- function(ns){ +importDataDialog <- function(ns) { modalDialog( - useShinyjs(), + shinyjs::useShinyjs(), title = "Import Data", footer = tagList( - actionButton(ns("cancel"), "Cancel"), - actionButton(ns("accept"), "Accept") - ), - selectInput(ns("source"), "Source", choices = c("Pandora Platform" = "ckan","File" = "file", "URL" = "url")), - conditionalPanel( - condition = "input.source == 'ckan'", - ns = ns, - selectInput(ns("ckanRecord"), "Pandora dataset", choices = NULL), - selectizeInput(ns("ckanResource"), "Pandora dataset resource", choices = NULL + #actionButton(ns("addData"), "Add data"), + actionButton(ns("accept"), "Accept"), + actionButton(ns("cancel"), "Cancel")), + tabsetPanel(tabPanel("Select Data", + selectDataTab(ns = ns))#, + # tabPanel("Merge Data", + # mergeDataUI(ns("dataMerger"))) + ) + ) +} + +#' Select Data UI +#' +#' @param ns namespace +selectDataTab <- function(ns) { + tagList( + tags$br(), + fluidRow( + column(4, + # select source UI ---- + selectInput( + ns("source"), + "Source", + choices = c( + "Pandora Platform" = "ckan", + "File" = "file", + "URL" = "url" + ) + )), + column( + 8, + conditionalPanel( + condition = "input.source == 'ckan'", + ns = ns, + selectInput(ns("ckanRecord"), "Pandora dataset", choices = NULL), + selectizeInput(ns("ckanResource"), "Pandora dataset resource", choices = NULL) + ), + conditionalPanel(condition = "input.source == 'file'", + ns = ns, + fileInput(ns("file"), "File")), + conditionalPanel(condition = "input.source == 'url'", + ns = ns, + textInput(ns("url"), "URL")) ) ), - conditionalPanel( - condition = "input.source == 'file'", - ns = ns, - fileInput(ns("file"), "File") - ), - conditionalPanel( - condition = "input.source == 'url'", - ns = ns, - textInput(ns("url"), "URL") - ), - selectInput( - ns("type"), - "File type", - choices = c("xls(x)" = "xlsx", "csv", "ods", "txt"), - selected = "xlsx" - ), - conditionalPanel( - condition = paste0("input.type == 'csv' || input.type == 'txt'"), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput(ns("colSep"), "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput(ns("decSep"), "decimal separator:", value = ".")), - ns = ns + tags$hr(), + # specify file UI ---- + fluidRow(column( + 4, + selectInput( + ns("type"), + "File type", + choices = c("xls(x)" = "xlsx", "csv", "ods", "txt"), + selected = "xlsx" + ) ), + column( + 8, + conditionalPanel( + condition = paste0("input.type == 'csv' || input.type == 'txt'"), + fluidRow(column( + width = 5, + textInput(ns("colSep"), "column separator:", value = ",") + ), + column( + width = 5, + textInput(ns("decSep"), "decimal separator:", value = ".") + )), + ns = ns + ) + )), checkboxInput(ns("rownames"), "First column contains rownames"), - helpText( - "The first row in your file need to contain variable names." - ), + helpText("The first row in your file need to contain variable names."), div(class = "text-danger", uiOutput(ns("warning"))), div(class = "text-danger", uiOutput(ns("error"))), div(class = "text-success", textOutput(ns("success"))), - tableOutput(ns("preview")) + tags$br(), + tags$h5("Preview:"), + fluidRow(column(12, + dataTableOutput(ns( + "preview" + )))) ) } +#' Load Data Wrapper +#' +#' @inheritParams importDataServer +#' @param values (list) list with import specifications +#' @param filepath url or path +#' @param filename url or file name +#' @param type (character) file type input +#' @param sep (character) column separator input +#' @param dec (character) decimal separator input +#' @param withRownames (logical) contains rownames input +#' @param headOnly (logical) load only head (first n rows) of file +loadDataWrapper <- function(values, + filepath, + filename, + colNames, + type, + sep, + dec, + withRownames, + headOnly, + customWarningChecks, + customErrorChecks) { + df <- tryCatch( + loadData( + file = filepath, + type = type, + sep = sep, + dec = dec, + rownames = withRownames, + headOnly = headOnly + ), + error = function(e) { + values$errors <- c(values$errors, "Could not read in file.") + NULL + }, + warning = function(w) { + values$warnings <- c(values$warnings, "Could not read in file.") + NULL + } + ) + + #attr(df, "includeSd") <- isTRUE(input$includeSd) + + ## set colnames + if (!is.null(colNames)) { + colnames(df) <- rep("", ncol(df)) + mini <- min(length(colNames()), ncol(df)) + colnames(df)[seq_len(mini)] <- colNames()[seq_len(mini)] + } + + ## Import technically successful + values$fileName <- filename + values$dataImport <- as.data.frame(df) + + ## Import valid? + lapply(customWarningChecks, function(fun) { + res <- fun()(df) + if (!isTRUE(res)) { + values$warnings <- c(values$warnings, res) + } + }) + + lapply(customErrorChecks, function(fun) { + res <- fun()(df) + if (!isTRUE(res)) { + values$errors <- c(values$errors, res) + } + }) + + values +} + + loadData <- function(file, type, sep = ",", dec = ".", - rownames = FALSE) { + rownames = FALSE, + headOnly = FALSE) { # if(type == "csv" | type == "txt"){ # codepages <- setNames(iconvlist(), iconvlist()) # x <- lapply(codepages, function(enc) try(suppressWarnings({read.csv(file, @@ -320,7 +430,8 @@ loadData <- dec = dec, stringsAsFactors = FALSE, row.names = NULL, - fileEncoding = encTry + fileEncoding = encTry, + nrows = getNrow(headOnly, type) ) }), txt = suppressWarnings({ @@ -330,27 +441,27 @@ loadData <- dec = dec, stringsAsFactors = FALSE, row.names = NULL, - fileEncoding = encTry + fileEncoding = encTry, + nrows = getNrow(headOnly, type) ) }), - xlsx = read.xlsx(file), + xlsx = read.xlsx(file, rows = getNrow(headOnly, type)), xls = suppressWarnings({ - readxl::read_excel(file) + readxl::read_excel(file, n_max = getNrow(headOnly, type)) }), - ods = readODS::read_ods(file) + ods = readODS::read_ods(file, range = getNrow(headOnly, type)) ) if (is.null(data)) return(NULL) - - if (any(dim(data) == 1)) { - warning("Number of rows or columns equal to 1") + if (is.null(dim(data))) { + stop("Could not determine dimensions of data") return(NULL) } - if (is.null(dim(data))) { - stop("Could not determine dimensions of data") + if (any(dim(data) == 1)) { + warning("Number of rows or columns equal to 1") return(NULL) } @@ -368,3 +479,59 @@ loadData <- return(data) } + + +#' Cut All Strings +#' +#' @param df (data.frame) data.frame with character and non-character columns +#' @param cutAt (numeric) number of characters after which to cut the entries of an character-column +cutAllLongStrings <- function(df, cutAt = 50) { + cutStrings <- function(vec, cutAt) { + if (any(nchar(vec) > cutAt, na.rm = TRUE)) { + index <- !is.na(vec) & nchar(vec) > cutAt + vec[index] <- paste0(substr(vec[index], 1, cutAt), "...") + } + + vec + } + + df <- lapply(df, function(z) { + if (!is.character(z)) + return(z) + + cutStrings(z, cutAt = cutAt) + }) %>% + as.data.frame() + + dfColNames <- colnames(df) %>% + cutStrings(cutAt = max(10, (cutAt - 3))) + colnames(df) <- dfColNames + + df +} + + +#' get nRow +#' +#' @param headOnly (logical) if TRUE, set maximal number of rows to n +#' @param type (character) file type +#' @param n (numeric) maximal number of rows if headOnly +getNrow <- function(headOnly, type, n = 4) { + if (headOnly) { + if (type == "xlsx") + return(1:n) + else + if (type == "ods") + return(paste0("A1:C", n)) + else + return(n) + } else { + if (type %in% c("xlsx", "ods")) + return(NULL) + else + if (type == "xls") + return(Inf) + else + return(-999) + } +} diff --git a/R/02-module-mergeData.R b/R/02-module-mergeData.R new file mode 100644 index 00000000..86030051 --- /dev/null +++ b/R/02-module-mergeData.R @@ -0,0 +1,26 @@ +#' Merge Data UI +#' +#' UI of the merge data module +#' +#' @param id id of module +mergeDataUI <- function(id) { + ns <- NS(id) + + tagList( + selectInput(ns("mergeList"), "Select Datasets", choices = NULL), + verbatimTextOutput(ns("preview")) + ) +} + +#' Merge Data Server +#' +#' Server function of the merge data module +#' @param id id of module +mergeDataServer <- function(id) { + moduleServer( + id, + function(input, output, session) { + output$preview <- renderText({input$mergeList}) + } + ) +} diff --git a/R/03-dataExplorer.R b/R/03-dataExplorer.R index 935512a6..27657ad6 100644 --- a/R/03-dataExplorer.R +++ b/R/03-dataExplorer.R @@ -217,7 +217,7 @@ dataExplorerServer <- function(id) { isoDataRaw(d) }, value = 0.75, - message = 'Get remote data ...' + message = 'Get remote data from database ...' ) }) diff --git a/man/cutAllLongStrings.Rd b/man/cutAllLongStrings.Rd new file mode 100644 index 00000000..2416d435 --- /dev/null +++ b/man/cutAllLongStrings.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{cutAllLongStrings} +\alias{cutAllLongStrings} +\title{Cut All Strings} +\usage{ +cutAllLongStrings(df, cutAt = 50) +} +\arguments{ +\item{df}{(data.frame) data.frame with character and non-character columns} + +\item{cutAt}{(numeric) number of characters after which to cut the entries of an character-column} +} +\description{ +Cut All Strings +} diff --git a/man/getNrow.Rd b/man/getNrow.Rd new file mode 100644 index 00000000..e618964c --- /dev/null +++ b/man/getNrow.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{getNrow} +\alias{getNrow} +\title{get nRow} +\usage{ +getNrow(headOnly, type, n = 4) +} +\arguments{ +\item{headOnly}{(logical) if TRUE, set maximal number of rows to n} + +\item{type}{(character) file type} + +\item{n}{(numeric) maximal number of rows if headOnly} +} +\description{ +get nRow +} diff --git a/man/loadDataWrapper.Rd b/man/loadDataWrapper.Rd new file mode 100644 index 00000000..267563d9 --- /dev/null +++ b/man/loadDataWrapper.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{loadDataWrapper} +\alias{loadDataWrapper} +\title{Load Data Wrapper} +\usage{ +loadDataWrapper( + values, + filepath, + filename, + colNames, + type, + sep, + dec, + withRownames, + headOnly, + customWarningChecks, + customErrorChecks +) +} +\arguments{ +\item{values}{(list) list with import specifications} + +\item{filepath}{url or path} + +\item{filename}{url or file name} + +\item{colNames}{(reactive) use this for colnames of imported data} + +\item{type}{(character) file type input} + +\item{sep}{(character) column separator input} + +\item{dec}{(character) decimal separator input} + +\item{withRownames}{(logical) contains rownames input} + +\item{headOnly}{(logical) load only head (first n rows) of file} + +\item{customWarningChecks}{list of reactive functions which will be executed after importing of data. +functions need to return TRUE if check is successful or a character with a warning otherwise.} + +\item{customErrorChecks}{list of reactive functions which will be executed after importing of data. +functions need to return TRUE if check is successful or a character with a warning otherwise.} +} +\description{ +Load Data Wrapper +} diff --git a/man/mergeDataServer.Rd b/man/mergeDataServer.Rd new file mode 100644 index 00000000..daade2a2 --- /dev/null +++ b/man/mergeDataServer.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-module-mergeData.R +\name{mergeDataServer} +\alias{mergeDataServer} +\title{Merge Data Server} +\usage{ +mergeDataServer(id) +} +\arguments{ +\item{id}{id of module} +} +\description{ +Server function of the merge data module +} diff --git a/man/mergeDataUI.Rd b/man/mergeDataUI.Rd new file mode 100644 index 00000000..c819ecbf --- /dev/null +++ b/man/mergeDataUI.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-module-mergeData.R +\name{mergeDataUI} +\alias{mergeDataUI} +\title{Merge Data UI} +\usage{ +mergeDataUI(id) +} +\arguments{ +\item{id}{id of module} +} +\description{ +UI of the merge data module +} diff --git a/man/selectDataTab.Rd b/man/selectDataTab.Rd new file mode 100644 index 00000000..4313c978 --- /dev/null +++ b/man/selectDataTab.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-importData.R +\name{selectDataTab} +\alias{selectDataTab} +\title{Select Data UI} +\usage{ +selectDataTab(ns) +} +\arguments{ +\item{ns}{namespace} +} +\description{ +Select Data UI +} diff --git a/tests/testthat/test-module-importData.R b/tests/testthat/test-module-importData.R index 965ab838..279ceab2 100644 --- a/tests/testthat/test-module-importData.R +++ b/tests/testthat/test-module-importData.R @@ -22,7 +22,6 @@ test_that("Test module importData", { rownames = FALSE, accept = TRUE ) - expect_equal( names(session$returned()), "https://pandoradata.earth/dataset/cbbc35e0-af60-4224-beea-181be10f7f71/resource/f7581eb1-b2b8-4926-ba77-8bc92ddb4fdb/download/cima-humans.xlsx" @@ -30,6 +29,143 @@ test_that("Test module importData", { expect_true(all( c("Entry.ID", "Reference", "Link", "DOI") %in% names(session$returned()[[1]]) )) - expect_true(nrow(session$returned()[[1]]) > 0) + expect_true(nrow(session$returned()[[1]]) > 100) + + expect_equal( + colnames(session$returned()[[1]])[1:10], + c( + "Entry.ID", + "Submitter.ID", + "Context.ID", + "Individual.ID", + "Sample.ID", + "Sex", + "Age.Category", + "Min..Age.(yrs)", + "Max..Age.(yrs)", + "Sampled.Element" + ) + ) }) }) + +test_that("cutAllLongStrings function", { + testData <- + structure( + list( + Entry.ID = c(1, 2, 3, 4, 5, 6), + Context.ID = c( + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_ + ), + Individual.ID = c( + "Høre kranie", + "Ringebu 3A", + "Bergen", + "Uvdal", + "Ringebu 3B", + "102" + ), + Sample.ID = c(NA, NA, NA, + NA, NA, "VHM 24"), + Sex = c(NA, NA, NA, NA, NA, "M"), + Latitude = c( + 61.153097, + 61.527761, + 60.393642, + 60.273504, + 61.527761, + 58.385741 + ), + Longitude = c(8.80468, 10.14467, 5.319837, + 8.243344, 10.14467, 13.646216), + Reference = c( + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åberg, G., Fosse, G., Stray, H. (1998). Man, nutrition and mobility: A comparison of teeth and bone from the Medieval era and the present from Pb and Sr isotopes. The Science of the Total Environment 224: 109-119.", + "Åborg, D.C. (2013). Hierarchy through Diet. Stable isotope analysis of male graves of the estate church graveyard in Varnhem. Unpublished BA dissertation: Stockholm University." + ), + Link = c( + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "https://www.sciencedirect.com/science/article/pii/S0048969798003477", + "http://www.diva-portal.org/smash/record.jsf?pid=diva2%3A622264&dswid=-9506" + ), + Publication.Year = c(1998, + 1998, 1998, 1998, 1998, 2013), + IRMS.Lab.Institution.Stable.Sulphur.Measurement = c(NA, + NA, NA, NA, NA, "Stockholm University") + ), + row.names = c(NA, 6L), + class = "data.frame" + ) + + expect_equal( + cutAllLongStrings(testData, cutAt = 30), + structure( + list( + Entry.ID = c(1, 2, 3, 4, 5, 6), + Context.ID = c( + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_, + NA_character_ + ), + Individual.ID = c( + "Høre kranie", + "Ringebu 3A", + "Bergen", + "Uvdal", + "Ringebu 3B", + "102" + ), + Sample.ID = c(NA, NA, NA, NA, + NA, "VHM 24"), + Sex = c(NA, NA, NA, NA, NA, "M"), + Latitude = c( + 61.153097, + 61.527761, + 60.393642, + 60.273504, + 61.527761, + 58.385741 + ), + Longitude = c(8.80468, + 10.14467, 5.319837, 8.243344, 10.14467, 13.646216), + Reference = c( + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åberg, G., Fosse, G., Stray, H...", + "Åborg, D.C. (2013). Hierarchy ..." + ), + Link = c( + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "https://www.sciencedirect.com/...", + "http://www.diva-portal.org/sma..." + ), + Publication.Year = c(1998, + 1998, 1998, 1998, 1998, 2013), + IRMS.Lab.Institution.Stable... = c(NA, + NA, NA, NA, NA, "Stockholm University") + ), + class = "data.frame", + row.names = c(NA, + -6L) + ) + ) +}) From d6f2c74c0dc447e68d18934c0ea8c2e31ddf7bea Mon Sep 17 00:00:00 2001 From: Jian Roachell Date: Wed, 22 Jun 2022 14:31:23 +0200 Subject: [PATCH 6/8] name change in OperatoR --- R/03-mapDiff.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/03-mapDiff.R b/R/03-mapDiff.R index acbf4eaf..ae279031 100644 --- a/R/03-mapDiff.R +++ b/R/03-mapDiff.R @@ -18,7 +18,7 @@ modelResultsDiffUI <- function(id, title = ""){ selectInput(ns("dataSource"), "Data source", choices = c("Create map" = "create", - "Saved map" = "model"), + "View single map" = "model"), selected = "db"), conditionalPanel( condition = "input.dataSource == 'model'", @@ -258,7 +258,7 @@ mapDiff <- function(input, output, session, savedMaps, fruitsData){ savedMaps()[[as.numeric(input$targetMap2)]]$predictions, operation = input$operation)), value = 0, - message = "Generating difference map" + message = "Generating map" ) } }) From 737fdaf617746038d2873c4437d4a54d080fcb42 Mon Sep 17 00:00:00 2001 From: Jian Roachell Date: Thu, 23 Jun 2022 14:53:07 +0200 Subject: [PATCH 7/8] name changes --- DESCRIPTION | 2 +- R/03-mapDiff.R | 2 +- inst/app/server.R | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 92a3b728..552686a6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: MpiIsoApp Title: Shiny App for Isotopes data base -Version: 22.06.3 +Version: 22.06.4 Author: INWT Statistics GmbH Maintainer: INWT Description: Shiny App contains: a data explorer tab, an interactive map and a static map, which should present model results. diff --git a/R/03-mapDiff.R b/R/03-mapDiff.R index ae279031..987c0bba 100644 --- a/R/03-mapDiff.R +++ b/R/03-mapDiff.R @@ -18,7 +18,7 @@ modelResultsDiffUI <- function(id, title = ""){ selectInput(ns("dataSource"), "Data source", choices = c("Create map" = "create", - "View single map" = "model"), + "Load OperatoR map" = "model"), selected = "db"), conditionalPanel( condition = "input.dataSource == 'model'", diff --git a/inst/app/server.R b/inst/app/server.R index 2a81266b..695f7c1e 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -40,7 +40,7 @@ server <- function(input, output, session) { ) } else { appendTab(inputId = "tab", - savedMapsTabUI("svmt", "Saved maps") + savedMapsTabUI("svmt", "Saved/Create maps") ) } From 29fad2da2edcc26d98f06285cee581279772497b Mon Sep 17 00:00:00 2001 From: Jian Roachell Date: Tue, 12 Jul 2022 09:55:55 +0200 Subject: [PATCH 8/8] description udpate --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 552686a6..d8f89043 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: MpiIsoApp Title: Shiny App for Isotopes data base -Version: 22.06.4 +Version: 22.07.3 Author: INWT Statistics GmbH Maintainer: INWT Description: Shiny App contains: a data explorer tab, an interactive map and a static map, which should present model results.