diff --git a/DESCRIPTION b/DESCRIPTION index 8627616e..b41d8a9b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: safetyGraphics Title: Create Interactive Graphics Related to Clinical Trial Safety -Version: 0.8.1 +Version: 0.9.0 Authors@R: c( person("Jeremy", "Wildfire", email = "jeremy_wildfire@rhoworld.com", role = c("cre","aut")), person("Becca", "Krouse", role="aut"), @@ -35,5 +35,7 @@ Imports: rmarkdown, rlang, tibble, - utils + utils, + haven, + shinyWidgets VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index cb58fb76..3acbd77b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ import(rmarkdown) import(shinyjs) importFrom(dplyr,"filter") importFrom(dplyr,filter) +importFrom(haven,read_sas) importFrom(magrittr,"%>%") importFrom(purrr,keep) importFrom(purrr,map) @@ -25,6 +26,7 @@ importFrom(purrr,map_lgl) importFrom(rlang,.data) importFrom(rlang,parse_expr) importFrom(shiny,runApp) +importFrom(shinyWidgets,materialSwitch) importFrom(stringr,str_detect) importFrom(stringr,str_split) importFrom(stringr,str_subset) diff --git a/R/detectStandard.R b/R/detectStandard.R index 6a7acf5a..da4a806c 100644 --- a/R/detectStandard.R +++ b/R/detectStandard.R @@ -1,16 +1,16 @@ #' Detect the data standard used for a data set #' -#' This function attempts to detect the data CDISC clinical standard used in a given R data frame. +#' This function attempts to detect the clinical data standard used in a given R data frame. #' -#' This function compares the columns in the provided \code{"data"} with the required columns for a given data standard/domain combination. The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, "labs" is the only domain supported. +#' This function compares the columns in the provided \code{"data"} with the required columns for a given data standard/domain combination. The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data by default. Additional standards can be added by modifying the \code{"standardMetadata"} data set included as part of this package. Currently, "labs" is the only domain supported. #' #' @param data A data frame in which to detect the data standard #' @param includeFields specifies whether to check the data set for field level data in addition to columns. Default: \code{TRUE}. #' @param domain The data domain for the data set provided. Default: \code{"labs"}. -#' @return A list containing the matching \code{"standard"} ("ADaM", "SDTM" or "None") and a list of \code{"details"} describing each standard considered. +#' @return A list containing the matching \code{"standard"} from \code{"standardMetadata"} and a list of \code{"details"} describing each standard considered. #' @examples -#' detectStandard(adlbc)[["standard"]] #AdAM +#' detectStandard(adlbc)[["standard"]] #adam #' detectStandard(iris)[["standard"]] #none #' #' \dontrun{ @@ -26,28 +26,45 @@ detectStandard <- function(data, includeFields=TRUE, domain="labs"){ ) - # Create placeholder list, with Standard = None. + # Create placeholder list, with Standard = none. + available_standards <- safetyGraphics::standardsMetadata %>% select(-.data$text_key) %>% names standard_list <- list() standard_list[["details"]] = list() - standard_list[["details"]][["ADaM"]]<-evaluateStandard(data,standard="ADaM", includeFields=includeFields, domain=domain) - standard_list[["details"]][["SDTM"]]<-evaluateStandard(data,standard="SDTM", includeFields=includeFields, domain=domain) + standard_list[["standard"]] = "none" + standard_list[["standard_percent"]] = 0 + + for(standard in available_standards){ + # evaluate the current standard and save the result + standard_list[["details"]][[standard]]<-evaluateStandard(data,standard=standard, includeFields=includeFields, domain=domain) + + # if the current standard is a better match, use it as the overall standard + # if there is a tie, don't change the standard - this means the column order in standardMetadata breaks ties! + current_percent <- standard_list[["details"]][[standard]][["match_percent"]] + overall_percent <- standard_list[["standard_percent"]] + if(current_percent > overall_percent){ + standard_list[["standard"]] <- standard + standard_list[["standard_percent"]] <- current_percent + } + } # Determine the final standard - if(standard_list[["details"]][["SDTM"]][["match"]] == "Full"){ - standard_list[["standard"]]<- "SDTM" - } else if(standard_list[["details"]][["ADaM"]][["match"]] == "Full"){ - standard_list[["standard"]]<- "ADaM" - } else if(standard_list[["details"]][["SDTM"]][["match"]] == "Partial" | - standard_list[["details"]][["ADaM"]][["match"]] == "Partial"){ - standard_list[["standard"]] <- ifelse( - length(standard_list[["details"]][["ADaM"]][["valid_count"]]) > - length(standard_list[["details"]][["SDTM"]][["valid_count"]]), - "ADaM" , "SDTM" #SDTM if they are equal - ) - - } else { - standard_list[["standard"]]<-"None" - } + + # TODO: write a general algorithm to do this ... + # if(standard_list[["details"]][["sdtm"]][["match"]] == "Full"){ + # standard_list[["standard"]]<- "sdtm" + # } else if(standard_list[["details"]][["adam"]][["match"]] == "Full"){ + # standard_list[["standard"]]<- "adam" + # } else if(standard_list[["details"]][["sdtm"]][["match"]] == "Partial" | + # standard_list[["details"]][["adam"]][["match"]] == "Partial"){ + # standard_list[["standard"]] <- ifelse( + # length(standard_list[["details"]][["adam"]][["valid_count"]]) > + # length(standard_list[["details"]][["sdtm"]][["valid_count"]]), + # "adam" , "sdtm" #SDTM if they are equal + # ) + # + # } else { + # standard_list[["standard"]]<-"None" + # } return(standard_list) } diff --git a/R/evaluateStandard.R b/R/evaluateStandard.R index ae820df5..ffe13068 100644 --- a/R/evaluateStandard.R +++ b/R/evaluateStandard.R @@ -7,7 +7,7 @@ #' @param includeFields should field level data be evaluated? #' @param domain data domain. "labs" only for now. #' -#' @return a list describing to what degree the data set matches the data standard. The "match" property describes compliance with the standard as "Full", "Partial" or "None". The "checks" property is a list of the data elements expected for the standard and whether they are "valid" in the given data set. "valid_checks" and "invalid_checks" provide counts of the specified checks. +#' @return a list describing to what degree the data set matches the data standard. The "match" property describes compliance with the standard as "full", "partial" or "none". The "checks" property is a list of the data elements expected for the standard and whether they are "valid" in the given data set. "total_checks", "valid_checks" and "invalid_checks" provide counts of the specified checks. "match_percent" is calculated as valid_checks/total_checks. #' #' @examples #' safetyGraphics:::evaluateStandard(data=adlbc, standard="adam") # Match is TRUE @@ -26,8 +26,7 @@ evaluateStandard <- function(data, standard, includeFields=TRUE, domain="labs"){ is.data.frame(data), is.character(standard), is.logical(includeFields), - is.character(domain), - tolower(standard) %in% c("adam","sdtm") + is.character(domain) ) standard<-tolower(standard) @@ -43,7 +42,8 @@ evaluateStandard <- function(data, standard, includeFields=TRUE, domain="labs"){ mutate(type = ifelse(.data$column_mapping, "column", "field")) %>% rowwise %>% mutate(field_column_name = ifelse(.data$field_mapping, getSettingsMetadata(cols=standard, text_keys=.data$field_column_key),"")) %>% - mutate(valid = ifelse(.data$column_mapping, + mutate( + valid = ifelse(.data$column_mapping, hasColumn(data=data, columnName=.data$standard_val), hasField(data=data, columnName=.data$field_column_name, fieldValue=.data$standard_val) )) %>% @@ -54,20 +54,20 @@ evaluateStandard <- function(data, standard, includeFields=TRUE, domain="labs"){ standardChecks <- standardChecks %>% filter(.data$type != "field") } - # compare_summary[["checks"]] <- split(standardChecks, seq(nrow(standardChecks)))%>%map(~as.list(.)) #coerce to list of lists? - compare_summary[["checks"]] <- standardChecks #or just keep the tibble ... + compare_summary[["checks"]] <- standardChecks # count valid/invalid data elements + compare_summary[["total_count"]] <- standardChecks %>% nrow() compare_summary[["valid_count"]] <- standardChecks %>% filter(.data$valid) %>% nrow() compare_summary[["invalid_count"]] <- standardChecks %>% filter(!.data$valid) %>% nrow() - - + compare_summary[["match_percent"]] <- compare_summary[["valid_count"]] / compare_summary[["total_count"]] + if (compare_summary[["invalid_count"]]==0) { - compare_summary[["match"]] <- "Full" + compare_summary[["match"]] <- "full" } else if(compare_summary[["valid_count"]]>0) { - compare_summary[["match"]] <- "Partial" + compare_summary[["match"]] <- "partial" } else { - compare_summary[["match"]] <- "None" + compare_summary[["match"]] <- "none" } return(compare_summary) diff --git a/R/getSettingsMetadata.R b/R/getSettingsMetadata.R index 14257138..7ae8aec8 100644 --- a/R/getSettingsMetadata.R +++ b/R/getSettingsMetadata.R @@ -5,6 +5,7 @@ #' @param charts optional vector of chart names used to filter the metadata. Exact matches only (case-insensitive). All rows returned by default. #' @param text_keys optional vector of keys used to filter the metadata. Partial matches for any of the strings are returned (case-insensitive). All rows returned by default. #' @param filter_expr optional filter expression used to subset the data. +#' @param add_standards should data standard info stored in standardsMetadata be included #' @param cols optional vector of columns to return from the metadata. All columns returned by default. #' @param metadata metadata data frame to be queried #' @@ -27,9 +28,15 @@ #' #' @export -getSettingsMetadata<-function(charts=NULL, text_keys=NULL, cols=NULL, filter_expr=NULL, metadata = safetyGraphics::settingsMetadata){ +getSettingsMetadata<-function(charts=NULL, text_keys=NULL, cols=NULL, filter_expr=NULL, add_standards=TRUE, metadata = safetyGraphics::settingsMetadata){ - md <- metadata + md <- metadata %>% mutate(text_key=as.character(.data$text_key)) + + if(add_standards){ + ms<-safetyGraphics::standardsMetadata %>% mutate(text_key=as.character(.data$text_key)) + md<-md%>%left_join(ms, by="text_key") + } + all_columns <- names(md) #filter the metadata based on the charts option (if any) diff --git a/R/safetyGraphicsApp.R b/R/safetyGraphicsApp.R index 4b29d35b..3190583c 100644 --- a/R/safetyGraphicsApp.R +++ b/R/safetyGraphicsApp.R @@ -9,12 +9,16 @@ #' @importFrom purrr map keep #' @importFrom magrittr "%>%" #' @import rmarkdown +#' @importFrom haven read_sas +#' @importFrom shinyWidgets materialSwitch #' #' @export #' -safetyGraphicsApp <- function(maxFileSize = 20) { +safetyGraphicsApp <- function(maxFileSize = NULL) { #increase maximum file upload limit - options(shiny.maxRequestSize=(maxFileSize*1024^2)) + if(!is.null(maxFileSize)){ + options(shiny.maxRequestSize=(maxFileSize*1024^2)) + } path <- system.file("eDISH_app", package = "safetyGraphics") shiny::runApp(path, launch.browser = TRUE) diff --git a/R/settingsMetadata.R b/R/settingsMetadata.R index d74a076a..92882d95 100644 --- a/R/settingsMetadata.R +++ b/R/settingsMetadata.R @@ -14,8 +14,7 @@ #' \item{column_type}{Expected type for the data column values. Should be "character","logical" or "numeric"} #' \item{field_mapping}{Flag indicating whether the setting corresponds to a field-level mapping in the data} #' \item{field_column_key}{Key for the column that provides options for the field-level mapping in the data} -#' \item{adam}{Settings values for the ADaM standard} -#' \item{sdtm}{Settings values for the SDTM standard} +#' \item{setting_cat}{Setting category (data, measure, appearance)} #' } #' #' @source Created for this package diff --git a/R/standardsMetadata.R b/R/standardsMetadata.R new file mode 100644 index 00000000..c1ffaac0 --- /dev/null +++ b/R/standardsMetadata.R @@ -0,0 +1,13 @@ +#' Standards Metadata +#' +#' Metadata about the data standards used to configure safetyGraphics charts. One record per unique setting. Columns contain default setting values for clinical data standards, like the CDISC "adam" and "sdtm" standards. +#' +#' @format A data frame with 25 rows and 3 columns +#' \describe{ +#' \item{text_key}{Text key indicating the setting name. \code{'--'} delimiter indicates a nested setting} +#' \item{adam}{Settings values for the ADaM standard} +#' \item{sdtm}{Settings values for the SDTM standard} +#' } +#' +#' @source Created for this package +"standardsMetadata" \ No newline at end of file diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index 9449676f..de6c8b5f 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -3,5 +3,8 @@ library(usethis) ablbc <- read.csv("data-raw/adlbc.csv") usethis::use_data(adlbc, overwrite = TRUE) -settingsMetadata<- read.csv("data-raw/settingsMetadata.csv") +settingsMetadata <- read.csv("data-raw/settingsMetadata.csv") usethis::use_data(settingsMetadata, overwrite = TRUE) + +standardsMetadata <- read.csv("data-raw/standardsMetadata.csv") +usethis::use_data(standardsMetadata, overwrite = TRUE) \ No newline at end of file diff --git a/data-raw/settingsMetadata.csv b/data-raw/settingsMetadata.csv index ce95c2f3..ac64c845 100644 --- a/data-raw/settingsMetadata.csv +++ b/data-raw/settingsMetadata.csv @@ -1,26 +1,26 @@ -chart_edish,text_key,label,description,setting_type,setting_required,column_mapping,column_type,field_mapping,field_column_key,adam,sdtm -TRUE,id_col,ID column,Unique subject identifier variable name.,character,TRUE,TRUE,character,FALSE,,USUBJID,USUBJID -TRUE,value_col,Value column,Lab result variable name.,character,TRUE,TRUE,numeric,FALSE,,AVAL,STRESN -TRUE,measure_col,Measure column,Lab measure variable name,character,TRUE,TRUE,character,FALSE,,PARAM,TEST -TRUE,normal_col_low,Lower Limit of Normal column,Lower limit of normal variable name,character,TRUE,TRUE,numeric,FALSE,,A1LO,STNRLO -TRUE,normal_col_high,Upper Limit of Normal column,Upper limit of normal variable name,character,TRUE,TRUE,numeric,FALSE,,A1HI,STNRHI -TRUE,studyday_col,Study Day column,Visit day variable name,character,TRUE,TRUE,numeric,FALSE,,ADY,DY -TRUE,visit_col,Visit column,Visit variable name,character,FALSE,TRUE,character,FALSE,,VISIT,VISIT -TRUE,visitn_col,Visit Number column,Visit number variable name,character,FALSE,TRUE,numeric,FALSE,,VISITNUM,VISITNUM -TRUE,filters,Filters columns,"An optional list of specifications for filters. Each filter is a nested, named list (containing the filter value column: 'value_col' and associated label: 'label') within the larger list.",vector,FALSE,TRUE,NA,FALSE,,, -TRUE,group_cols,Group columns,"An optional list of specifications for grouping columns. Each group column is a nested, named list (containing the group variable column: 'value_col' and associated label: 'label') within the larger list. ",vector,FALSE,TRUE,NA,FALSE,,, -TRUE,measure_values--ALT,Alanine Aminotransferase value,Value used for Alanine Aminotransferase in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,Alanine Aminotransferase (U/L),"Aminotransferase, alanine (ALT)" -TRUE,measure_values--AST,Aspartate Aminotransferase value,Value used for Aspartate Aminotransferase in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,Aspartate Aminotransferase (U/L),"Aminotransferase, aspartate (AST)" -TRUE,measure_values--TB,Total Bilirubin value,Value used for Total Bilirubin in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,Bilirubin (umol/L),Total Bilirubin -TRUE,measure_values--ALP,Alkaline Phosphatase value,Value used for Alkaline Phosphatase in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,Alkaline Phosphatase (U/L),Alkaline phosphatase (ALP) -TRUE,baseline--value_col,Baseline column,An optional list defining which column represent the baseline visit(s) of the study.,character,FALSE,TRUE,NA,FALSE,,, -TRUE,baseline--values,Baseline values,An optional list defining which values (one or more) represent the baseline visit(s) of the study.,vector,FALSE,FALSE,NA,TRUE,baseline--value_col,, -TRUE,analysisFlag--value_col,Analysis Flag column,An optional list defining which column should be used in eDish and mDish analyses.,character,FALSE,TRUE,NA,FALSE,,, -TRUE,analysisFlag--values,Analysis Flag values,An optional list defining which values should be used in eDish and mDish analyses.,vector,FALSE,FALSE,NA,TRUE,analysisFlag--value_col,, -TRUE,x_options,X axis options,"Specifies variable options for the x-axis using the key values from measure_values (e.g. 'ALT'). When multiple options are specified, a control allowing the user to interactively change the x variable is shown. ",vector,FALSE,FALSE,NA,FALSE,,, -TRUE,y_options,Y axis options,"Specifies variable options for the y-axis using the key values from measure_values (e.g. 'TB'). When multiple options are specified, a control allowing the user to interactively change the y variable is shown. ",vector,FALSE,FALSE,NA,FALSE,,, -TRUE,visit_window,Default Visit Window in Days ,Default visit window used to highlight eDish points where x and y measures occurred within the specified number of days. Editable by user after render. ,numeric,FALSE,FALSE,NA,FALSE,,, -TRUE,r_ratio_filter,Show R Ratio Filter?,Specifies whether the R Ratio filter should be shown. R ratio is defined as: (ALT value/ULN for ALT) / (ALP value/ULN for ALP).,logical,FALSE,FALSE,NA,FALSE,,, -TRUE,r_ratio_cut,Default R Ratio Cut,Default cut point for R Ratio filter. Ignored when r_ratio_filter = FALSE. User can update this setting via the UI when r_ratio_filter = TRUE. ,numeric,FALSE,FALSE,NA,FALSE,,, -TRUE,showTitle,Show Chart Title? ,Specifies whether the title should be drawn above the controls.,logical,FALSE,FALSE,NA,FALSE,,, -TRUE,warningText,Warning text,"Informational text to be displayed near the top of the controls (beneath the title, if any). No warning is displayed if warningText = ''. ",Character,FALSE,FALSE,NA,FALSE,,, \ No newline at end of file +chart_edish,text_key,label,description,setting_type,setting_required,column_mapping,column_type,field_mapping,field_column_key,setting_cat +TRUE,id_col,ID column,Unique subject identifier variable name.,character,TRUE,TRUE,character,FALSE,,data +TRUE,value_col,Value column,Lab result variable name.,character,TRUE,TRUE,numeric,FALSE,,data +TRUE,measure_col,Measure column,Lab measure variable name,character,TRUE,TRUE,character,FALSE,,data +TRUE,measure_values--ALT,Alanine Aminotransferase value,Value used for Alanine Aminotransferase in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,data +TRUE,measure_values--AST,Aspartate Aminotransferase value,Value used for Aspartate Aminotransferase in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,data +TRUE,measure_values--TB,Total Bilirubin value,Value used for Total Bilirubin in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,data +TRUE,measure_values--ALP,Alkaline Phosphatase value,Value used for Alkaline Phosphatase in the specified measure column,character,TRUE,FALSE,NA,TRUE,measure_col,data +TRUE,normal_col_low,Lower Limit of Normal column,Lower limit of normal variable name,character,TRUE,TRUE,numeric,FALSE,,data +TRUE,normal_col_high,Upper Limit of Normal column,Upper limit of normal variable name,character,TRUE,TRUE,numeric,FALSE,,data +TRUE,studyday_col,Study Day column,Visit day variable name,character,TRUE,TRUE,numeric,FALSE,,data +TRUE,visit_col,Visit column,Visit variable name,character,FALSE,TRUE,character,FALSE,,data +TRUE,visitn_col,Visit Number column,Visit number variable name,character,FALSE,TRUE,numeric,FALSE,,data +TRUE,filters,Filters columns,"An optional list of specifications for filters. Each filter is a nested, named list (containing the filter value column: 'value_col' and associated label: 'label') within the larger list.",vector,FALSE,TRUE,NA,FALSE,,data +TRUE,group_cols,Group columns,"An optional list of specifications for grouping columns. Each group column is a nested, named list (containing the group variable column: 'value_col' and associated label: 'label') within the larger list. ",vector,FALSE,TRUE,NA,FALSE,,data +TRUE,baseline--value_col,Baseline column,An optional list defining which column represent the baseline visit(s) of the study.,character,FALSE,TRUE,NA,FALSE,,data +TRUE,baseline--values,Baseline values,An optional list defining which values (one or more) represent the baseline visit(s) of the study.,vector,FALSE,FALSE,NA,TRUE,baseline--value_col,data +TRUE,analysisFlag--value_col,Analysis Flag column,An optional list defining which column should be used in eDish and mDish analyses.,character,FALSE,TRUE,NA,FALSE,,data +TRUE,analysisFlag--values,Analysis Flag values,An optional list defining which values should be used in eDish and mDish analyses.,vector,FALSE,FALSE,NA,TRUE,analysisFlag--value_col,data +TRUE,x_options,X axis options,"Specifies variable options for the x-axis using the key values from measure_values (e.g. 'ALT'). When multiple options are specified, a control allowing the user to interactively change the x variable is shown. ",vector,FALSE,FALSE,NA,FALSE,,measure +TRUE,y_options,Y axis options,"Specifies variable options for the y-axis using the key values from measure_values (e.g. 'TB'). When multiple options are specified, a control allowing the user to interactively change the y variable is shown. ",vector,FALSE,FALSE,NA,FALSE,,measure +TRUE,visit_window,Default Visit Window in Days ,Default visit window used to highlight eDish points where x and y measures occurred within the specified number of days. Editable by user after render. ,numeric,FALSE,FALSE,NA,FALSE,,appearance +TRUE,r_ratio_filter,Show R Ratio Filter?,Specifies whether the R Ratio filter should be shown. R ratio is defined as: (ALT value/ULN for ALT) / (ALP value/ULN for ALP).,logical,FALSE,FALSE,NA,FALSE,,appearance +TRUE,r_ratio_cut,Default R Ratio Cut,Default cut point for R Ratio filter. Ignored when r_ratio_filter = FALSE. User can update this setting via the UI when r_ratio_filter = TRUE. ,numeric,FALSE,FALSE,NA,FALSE,,appearance +TRUE,showTitle,Show Chart Title? ,Specifies whether the title should be drawn above the controls.,logical,FALSE,FALSE,NA,FALSE,,appearance +TRUE,warningText,Warning text,"Informational text to be displayed near the top of the controls (beneath the title, if any). No warning is displayed if warningText = ''. ",character,FALSE,FALSE,NA,FALSE,,appearance \ No newline at end of file diff --git a/data-raw/standardsMetadata.csv b/data-raw/standardsMetadata.csv new file mode 100644 index 00000000..b51715e9 --- /dev/null +++ b/data-raw/standardsMetadata.csv @@ -0,0 +1,26 @@ +text_key,sdtm,adam +id_col,USUBJID,USUBJID +value_col,STRESN,AVAL +measure_col,TEST,PARAM +normal_col_low,STNRLO,A1LO +normal_col_high,STNRHI,A1HI +studyday_col,DY,ADY +visit_col,VISIT,VISIT +visitn_col,VISITNUM,VISITNUM +filters,, +group_cols,, +measure_values--ALT,"Aminotransferase, alanine (ALT)",Alanine Aminotransferase (U/L) +measure_values--AST,"Aminotransferase, aspartate (AST)",Aspartate Aminotransferase (U/L) +measure_values--TB,Total Bilirubin,Bilirubin (umol/L) +measure_values--ALP,Alkaline phosphatase (ALP),Alkaline Phosphatase (U/L) +baseline--value_col,, +baseline--values,, +analysisFlag--value_col,, +analysisFlag--values,, +x_options,, +y_options,, +visit_window,, +r_ratio_filter,, +r_ratio_cut,, +showTitle,, +warningText,, \ No newline at end of file diff --git a/data/settingsMetadata.rda b/data/settingsMetadata.rda index c70c9c65..525b0f1d 100644 Binary files a/data/settingsMetadata.rda and b/data/settingsMetadata.rda differ diff --git a/data/standardsMetadata.rda b/data/standardsMetadata.rda new file mode 100644 index 00000000..4aaca682 Binary files /dev/null and b/data/standardsMetadata.rda differ diff --git a/inst/eDISH_app/global.R b/inst/eDISH_app/global.R index 2d4f75f9..a248f576 100644 --- a/inst/eDISH_app/global.R +++ b/inst/eDISH_app/global.R @@ -1,10 +1,15 @@ +# global.R code for safetyGraphics app +# - load all required libraries +# - source module functions library(safetyGraphics) library(shiny) +library(shinyWidgets) library(shinyjs) library(dplyr) library(purrr) library(stringr) library(DT) +library(haven) ## source modules source('modules/renderSettings/renderSettingsUI.R') diff --git a/inst/eDISH_app/modules/dataUpload/dataUpload.R b/inst/eDISH_app/modules/dataUpload/dataUpload.R index 57f4de9c..82ee0633 100644 --- a/inst/eDISH_app/modules/dataUpload/dataUpload.R +++ b/inst/eDISH_app/modules/dataUpload/dataUpload.R @@ -1,16 +1,45 @@ +#' Data upload module - server code +#' +#' This module creates the Data tab for the Shiny app. +#' +#' Workflow: +#' (1) A reactiveValues() list is created with example dataset as first entry. +#' The following information is included in list: +#' - dataset and name +#' - current (whether the dataset came from most recent upload) +#' - data standard / quality of match +#' (2) Upon user data upload: +#' - reactiveValues list is updated with information about new data. +#' - radio buttons are updated with new data choices +#' (3) When a new dataset is selected, the data preview output is invalidated +#' (4) When a new dataset is selected OR the standard changes (since these don't update at the same time), the +#' the settings object ("settings()") is invalidated. +#' (5) When a new dataset is selected OR the settings object is updated, the settings validation ("status()") is +#' invalidated. +#' +#' @param input Input objects from module namespace +#' @param output Output objects from module namespace +#' @param session An environment that can be used to access information and functionality relating to the session +#' +#' @return A list of reactive values, including: +#' \itemize{ +#' \item{"data_selected"}{A data frame selected by the user} +#' \item{"settings"}{Result from generateSettings() for data_selected} +#' \item{"status"}{Result from validateSettings() for data_selected and settings} +#' dataUpload <- function(input, output, session){ - + ns <- session$ns - + # initiate reactive values - list of uploaded data files # standard to imitate output of detectStandard.R - dd <- reactiveValues(data = list("Example data" = adlbc), current = 1, standard = list(list("standard" = "ADaM", "details" = list("ADaM"=list("match"="Full"))))) - + dd <- reactiveValues(data = list("Example data" = adlbc), current = 1, standard = list(list("standard" = "adam", "details" = list("adam"=list("match"="full"))))) + # modify reactive values when data is uploaded observeEvent(input$datafile,{ - + data_list <- list() - + ## data list for (i in 1:nrow(input$datafile)){ if (length(grep(".csv", input$datafile$name[i], ignore.case = TRUE)) > 0){ @@ -23,87 +52,87 @@ dataUpload <- function(input, output, session){ } # names names(data_list) <- input$datafile$name - + # append to existing reactiveValues list dd$data <- c(dd$data, data_list) - + # set dd$current to FALSE for previous & TRUE for current uploads dd$current <- c(rep(FALSE, length(dd$current)), rep(TRUE, length(data_list))) - + # run detectStandard on new data and save to dd$standard - + standard_list <- lapply(data_list, function(x){ detectStandard(x) }) - + #standard_list <- lapply(data_list, function(x){ detectStandard(x)$standard }) - + dd$standard <- c(dd$standard, standard_list) - + }) - - + + ### make a reactive combining dd$data & standard data_choices <- reactive({ - + req(dd$data) req(dd$standard) - + choices <- list() for (i in 1:length(dd$data)){ choices[[i]] <- names(dd$data)[i] } for (i in 1:length(dd$data)){ - + temp_standard <- dd$standard[[i]]$standard - - if(temp_standard == "None") { + standard_label <- ifelse(temp_standard=="adam","AdAM",ifelse(temp_standard=="sdtm","SDTM",temp_standard)) + if(temp_standard == "none") { names(choices)[i] <- paste0("

", names(dd$data)[i], " - No Standard Detected

") - } else if (dd$standard[[i]]$details[[temp_standard]]$match == "Full") { - names(choices)[i] <- paste0("

", names(dd$data)[i], " - ", dd$standard[[i]]$standard, "

") + } else if (dd$standard[[i]]$details[[temp_standard]]$match == "full") { + names(choices)[i] <- paste0("

", names(dd$data)[i], " - ", standard_label, "

") # If partial data spec match - give the fraction of variables matched } else { - + valid_count <- dd$standard[[i]]$details[[temp_standard]]$valid_count total_count <- dd$standard[[i]]$details[[temp_standard]]$invalid_count + valid_count - + fraction_cols <- paste0(valid_count, "/" ,total_count) names(choices)[i] <- paste0("

", names(dd$data)[i], " - ", "Partial ", - dd$standard[[i]]$standard, " (", fraction_cols, " data settings)", "

") + standard_label, " (", fraction_cols, " data settings)", "

") } } return(choices) }) - + # update radio buttons to display dataset names and standards for selection observeEvent(input$datafile, { req(data_choices()) vals <- data_choices() names(vals) <- NULL names <- lapply(names(data_choices()), HTML) - + prev_sel <- lapply(reactiveValuesToList(input), unclass)$select_file # retain previous selection - + updateRadioButtons(session, "select_file", choiceNames = names, choiceValues = vals, selected = prev_sel) - + }) - + # get selected dataset when selection changes data_selected <- eventReactive(input$select_file, { isolate({index <- which(names(dd$data)==input$select_file)[1]}) dd$data[[index]] }) - + # upon a dataset being uploaded and selected, generate data preview output$datapreview_header <- renderUI({ data_selected() isolate(data_name <- input$select_file) h3(paste("Data Preview for", data_name)) }) - + output$data_preview <- DT::renderDataTable({ DT::datatable(data = data_selected(), caption = isolate(input$select_file), @@ -112,54 +141,54 @@ dataUpload <- function(input, output, session){ class="compact", extensions = "Scroller", options = list(scrollY=400, scrollX=TRUE)) }) - - + + # upon a dataset being selected, grab its standard standard <- eventReactive(data_selected(), { index <- which(names(dd$data)==input$select_file)[1] dd$standard[[index]] }) - + # upon a dataset being selected, use generateSettings() to produce a settings obj settings <- eventReactive(c(data_selected(), standard()), { - + current_standard <- standard()$standard - - if (! current_standard=="None"){ - partial <- ifelse(standard()$details[[current_standard]]$match == "Partial", TRUE, FALSE) - + + if (! current_standard=="none"){ + partial <- ifelse(standard()$details[[current_standard]]$match == "partial", TRUE, FALSE) + if (partial) { - partial_keys <- standard()$details[[current_standard]]$checks %>% + partial_keys <- standard()$details[[current_standard]]$checks %>% filter(valid==TRUE) %>% - select(text_key) %>% + select(text_key) %>% pull() - + generateSettings(standard=current_standard, chart="eDish", partial=partial, partial_keys = partial_keys) - + } else { generateSettings(standard=current_standard, chart="eDish") - } + } } else { generateSettings(standard=current_standard, chart="eDish") } }) - + # run validateSettings(data, standard, settings) and return a status status <- reactive({ req(data_selected()) req(settings()) - validateSettings(data_selected(), + validateSettings(data_selected(), settings(), - chart="eDish") + chart="eDish") }) - + exportTestValues(status = { status() }) ### return selected data, settings, and status to server return(list(data_selected = reactive(data_selected()), settings = reactive(settings()), status = reactive(status()))) - -} \ No newline at end of file + +} diff --git a/inst/eDISH_app/modules/dataUpload/dataUploadUI.R b/inst/eDISH_app/modules/dataUpload/dataUploadUI.R index e214b5b8..cb5b6ccb 100644 --- a/inst/eDISH_app/modules/dataUpload/dataUploadUI.R +++ b/inst/eDISH_app/modules/dataUpload/dataUploadUI.R @@ -1,21 +1,34 @@ +#' Data upload module - UI code +#' +#' This module creates the Data tab for the Shiny app. +#' +#' The UI contains: +#' - a file upload control +#' - radio buttons for selecting from the available datasets +#' - raw data preview. +#' +#' @param id The module-specific ID that will get pre-pended to all element IDs +#' +#' @return The UI for the Data tab +#' dataUploadUI <- function(id){ - + ns <- NS(id) - - tagList( + + tagList( fluidRow( column(3, wellPanel( - h3("Data upload"), + h3("Data upload"), fileInput(ns("datafile"), "Upload a csv or sas7bdat file",accept = c(".sas7bdat", ".csv"), multiple = TRUE), - radioButtons(ns("select_file"),"Select file for eDISH chart", + radioButtons(ns("select_file"),"Select file for eDISH chart", choiceNames = list(HTML("

Example data - ADaM

")), choiceValues = "Example data") ) ), - column(6, + column(6, fluidRow( - wellPanel( + wellPanel( uiOutput(ns("datapreview_header")), div(DT::dataTableOutput(ns("data_preview")), style = "font-size: 75%") ) @@ -23,5 +36,5 @@ dataUploadUI <- function(id){ ) ) ) - -} \ No newline at end of file + +} diff --git a/inst/eDISH_app/modules/renderChart/renderEDishChart.R b/inst/eDISH_app/modules/renderChart/renderEDishChart.R index 15470cca..b900e9bf 100644 --- a/inst/eDISH_app/modules/renderChart/renderEDishChart.R +++ b/inst/eDISH_app/modules/renderChart/renderEDishChart.R @@ -1,3 +1,19 @@ +#' Render eDISH chart - server code +#' +#' This module creates the Chart tab for the Shiny app, which contains the interactive eDISH graphic. +#' +#' Workflow: +#' (1) A change in `data`, `settings`, or `valid` invalidates the eDISH chart output +#' (2) Upon a change in `valid`, the export chart functionality is conditionally made available or unavailable to user +#' (3) If "export chart" button is pressed, data and settings are passed to the parameterized report, knitted using +#' Rmarkdown, and downloaded to user computer. +#' +#' @param input Input objects from module namespace +#' @param output Output objects from module namespace +#' @param session An environment that can be used to access information and functionality relating to the session +#' @param data A data frame +#' @param valid A logical indicating whether data/settings combination is valid for chart + renderEDishChart <- function(input, output, session, data, settings, valid){ ns <- session$ns diff --git a/inst/eDISH_app/modules/renderChart/renderEDishChartUI.R b/inst/eDISH_app/modules/renderChart/renderEDishChartUI.R index 8751609c..62a8fe14 100644 --- a/inst/eDISH_app/modules/renderChart/renderEDishChartUI.R +++ b/inst/eDISH_app/modules/renderChart/renderEDishChartUI.R @@ -1,3 +1,11 @@ +#' Render eDISH chart - UI code +#' +#' This module creates the Chart tab for the Shiny app, which contains the interactive eDISH graphic. + +#' @param id The module-specific ID that will get pre-pended to all element IDs +#' +#' @return The UI for the Chart tab +#' renderEDishChartUI <- function(id){ ns <- NS(id) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 41e62c3f..014d686c 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -1,189 +1,143 @@ -source("modules/renderSettings/util/labelSetting.R") -source("modules/renderSettings/util/flagSetting.R") +# Functions to include +source("modules/renderSettings/util/createSettingsSection.R") +source("modules/renderSettings/util/createSettingLabel.R") +source("modules/renderSettings/util/createControl.R") +source("modules/renderSettings/util/createSettingsUI.R") source("modules/renderSettings/util/updateSettingStatus.R") +#' Render Settings module - Server code +#' +#' This module creates the Settings tab for the Shiny app. +#' +#' Workflow: +#' (1) Reactive input_names() contains names of settings related to selected charts. When a user changes +#' chart selections, input_names() is invalidated. +#' (2) A change in input_names(), `data`, or `settings` invalidates the following: +#' - renderUI associated with data mapping settings +#' - renderUI associated with measure settings +#' - renderUI associated with appearance settings +#' (3) These renderUI's call upon the createSettingsUI() function and will update +#' even when settings tab not in view. They will create and populate the UI for all related settings. +#' (4) Field-level inputs are updated upon any of the following events: +#' - a change in selected data +#' - change in selected chart(s) +#' - change in column-level input selection +#' update includes: +#' - Deactivate/activate field-level selector based on whether column-level input has been selected +#' - Data choices for field-level inputs based on selected column-level input +#' (5) A reactive representing the new settings object (settings_new()) is created based on UI selections. This object is invalidated +#' when ANY input changes. +#' (6) A reactive representing the new data/settings validation (status_new()) is created based on data and updated settings object. +#' A change in data OR updated settings object invalidated this reactive. +#' (7) Upon a change in the new validation (status_new() and derived status_df()), updated status messages are +#' printed on UI by calling updateSettingStatus(). ALL messages are re-printed at once. +#' +#' @param input Input objects from module namespace +#' @param output Output objects from module namespace +#' @param session An environment that can be used to access information and functionality relating to the session +#' @param data A data frame +#' @param settings Settings object that corresponds to data's standard - result of generateSettings(). +#' @param status A list describing the validation state for data/settings - result of validateSettings(). +#' +#' @return A list of reactive values, including: +#' \itemize{ +#' \item{"charts"}{A vector of chart(s) selected by the user} +#' \item{"settings"}{Upadted settings object based on UI/user selections} +#' \item{"status"}{Result from validateSettings() for originally selected data + updated settings object} +#' renderSettings <- function(input, output, session, data, settings, status){ + + ns <- session$ns + + #List of all inputs + input_names <- reactive({safetyGraphics:::getSettingsMetadata(charts=input$selected_charts, cols="text_key")}) + + + ###################################################################### + # create settings UI + # - chart selection -> gather all necessary UI elements + # - create elements based on metadata file + # - populate using data/settings + ###################################################################### + + output$data_mapping_ui <- renderUI({ + req(input$charts) + tagList(createSettingsUI(data=data(), settings = settings(), setting_cat_val = "data", charts=input$charts, ns=ns)) + }) + outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) + + output$measure_settings_ui <- renderUI({ + req(input$charts) + tagList(createSettingsUI(data=data(), settings = settings(), setting_cat_val = "measure", charts=input$charts, ns=ns)) + }) + outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) + output$appearance_settings_ui <- renderUI({ + req(input$charts) + tagList(createSettingsUI(data=data(), settings = settings(), setting_cat_val = "appearance", charts=input$charts, ns=ns)) + }) + outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE) - #TODO: Save to separate file - probably needs to be a module. - runCustomObserver<-function(name){ + + ###################################################################### + # Update field level inputs + # + # update field-level inputs if a column level setting changes + # dependent on change in data, chart selection, or column-level input + ###################################################################### + + observe({ - # Custom observer for measure_col - if(name=="measure_col"){ - observe({ - settings <- settings() - - req(input$measure_col) - - if (input$measure_col %in% colnames()){ - if (!is.null(settings$measure_col) && input$measure_col==settings$measure_col){ - choices_ast <- unique(c(settings$measure_values$AST, as.character(data()[,settings$measure_col]))) - choices_alt <- unique(c(settings$measure_values$ALT, as.character(data()[,settings$measure_col]))) - choices_tb <- unique(c(settings$measure_values$TB, as.character(data()[,settings$measure_col]))) - choices_alp <- unique(c(settings$measure_values$ALP, as.character(data()[,settings$measure_col]))) - - updateSelectizeInput(session, "measure_values--ALT", choices = choices_alt, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }'))) - updateSelectizeInput(session, "measure_values--AST", choices = choices_ast, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }'))) - updateSelectizeInput(session, "measure_values--TB", choices = choices_tb, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }'))) - updateSelectizeInput(session, "measure_values--ALP", choices = choices_alp, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }'))) - } else { - choices_ast <- unique(data()[,input$measure_col]) - choices_alt <- unique(data()[,input$measure_col]) - choices_tb <- unique(data()[,input$measure_col]) - choices_alp <- unique(data()[,input$measure_col]) - - updateSelectizeInput(session, "measure_values--ALT", choices = choices_alt, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }'))) - updateSelectizeInput(session, "measure_values--AST", choices = choices_ast, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }'))) - updateSelectizeInput(session, "measure_values--TB", choices = choices_tb, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }'))) - updateSelectizeInput(session, "measure_values--ALP", choices = choices_alp, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }'))) - } - } else { - updateSelectizeInput(session, "measure_values--ALT", choices = "") - updateSelectizeInput(session, "measure_values--AST", choices = "") - updateSelectizeInput(session, "measure_values--TB", choices = "") - updateSelectizeInput(session, "measure_values--ALP", choices = "") - } - - }) - } + column_keys <- getSettingsMetadata(charts=input$charts, + filter_expr = field_mapping==TRUE) %>% + pull(field_column_key) %>% + unique %>% + as.list() - # Custom observer for baseline - if(name=="baseline--value_col"){ - observe({ - settings <- settings() + lapply(column_keys, function(col){ + + col_quo <- enquo(col) + observeEvent(input[[col]],{ + + field_keys <- getSettingsMetadata(charts=input$charts, col = "text_key", + filter_expr = field_column_key==!!col) - req(input$`baseline--value_col`) - if (input$`baseline--value_col` %in% colnames()){ - if (!is.null(settings$baseline$value_col) && input$`baseline--value_col`==settings$baseline$value_col){ - choices <- unique(c(settings$baseline$values, as.character(data()[,settings$baseline$value_col]))) - choices <- sort(choices) - - updateSelectizeInput(session, "baseline--values", choices = choices, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }'))) - } else { - choices <- unique(data()[,input$`baseline--value_col`]) - choices <- sort(choices) - - updateSelectizeInput(session, "baseline--values", choices = choices, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }'))) - } - } else { - updateSelectizeInput(session, "baseline--values", choices = "") + # Toggle field-level inputs: + # ON - if column-level input is selected) + # OFF - if column-level input is not yet selected + for (fk in field_keys){ + toggleState(id = fk, condition = !input[[col]]=="") } - }) - } - - - # Custom observer for analysis population - if(name=="analysisFlag--value_col"){ - observe({ - settings <- settings() - - req(input$`analysisFlag--value_col`) - - if (input$`analysisFlag--value_col` %in% colnames()){ - if (!is.null(settings$analysisFlag$value_col) && input$`analysisFlag--value_col`==settings$analysisFlag$value_col){ - choices <- unique(c(settings$analysisFlag$values, as.character(data()[,settings$analysisFlag$value_col]))) - - updateSelectizeInput(session, "analysisFlag--values", choices = choices,, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }'))) - } else { - choices <- unique(data()[,input$`analysisFlag--value_col`]) - - updateSelectizeInput(session, "analysisFlag--values", choices = choices, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }'))) + + if (is.null(isolate(settings()[[col]])) || ! input[[col]] == isolate(settings()[[col]])){ + if (input[[col]] %in% colnames(data())){ + + choices <- unique(data()[,input[[col]]]) + + for (key in field_keys){ + updateSelectizeInput(session, inputId = key, choices = choices, + options = list(placeholder = "Please select a value", + onInitialize = I('function() { + this.setValue(""); + }'))) + } + } } - } else { - updateSelectizeInput(session, "analysisFlag--values", choices = "") - } - - }) - } - } #end runCustomObserver() - - ########################### - # Make updates to the UI - ########################### - ns <- session$ns - - - #Columns in the data - colnames <- reactive({names(data())}) - - #List of all inputs - #input_names <- reactive({safetyGraphics:::getSettingsMetadata(charts="eDiSH", cols="text_key")}) - input_names <- reactive({names(lapply(reactiveValuesToList(input), unclass))}) - #observe({print(input_names())}) + } + ) + }) + }) + + ###################################################################### # Fill settings object based on selections - # require that secondary inputs have been filled in before proceeding + # # update is triggered by any of the input selections changing - # - # NOTE: when data selection changes, the inputs are updating 1 by 1 - # Therefore, until the inputs are done updating based on new data, this object will be - # partially representing the old data, and partially representing the new data. - # not sure if this is the right place to do it...but can we clear out this object upon a data change and start over?? + ###################################################################### settings_new <- reactive({ - # print(input$id_col) settings <- list(id_col = input$id_col, value_col = input$value_col, @@ -236,13 +190,13 @@ renderSettings <- function(input, output, session, data, settings, status){ }) + ###################################################################### # validate new settings # the validation is run every time there is a change in data and/or settings. # - # NOTE: to prevent status updating as loop runs and fills in settings(), - # require the very last updated input to be available <- can't do this b/c we will have lots of - # null settings to start when no standard detected... - status_new <- reactive({ #eventReactive(settingsUI_list$settings,{ + ###################################################################### + + status_new <- reactive({ req(data()) req(settings_new()) name <- rev(isolate(input_names()))[1] @@ -261,123 +215,49 @@ renderSettings <- function(input, output, session, data, settings, status){ }) - #Setting Status information (from failed checks only) + ###################################################################### + # Setting validation status information + ###################################################################### status_df <- reactive({ req(status_new()) status_new()$checks %>% group_by(text_key) %>% mutate(num_fail = sum(valid==FALSE)) %>% + mutate(icon = ifelse(num_fail==0, "",""))%>% mutate(message_long = paste(message, collapse = " ") %>% trimws(), message_short = case_when( num_fail==0 ~ "OK", num_fail==1 ~ "1 failed check.", TRUE ~ paste(num_fail, "failed checks.") )) %>% - select(text_key, message_long, message_short, num_fail) %>% + select(text_key, icon, message_long, message_short, num_fail) %>% unique }) - + # for shiny tests exportTestValues(status_df = { status_df() }) - #List of required settings - req_settings <- safetyGraphics:::getSettingsMetadata() %>% - filter(chart_edish==TRUE & setting_required==TRUE) %>% - pull(text_key) - - #List of inputs with custom observers - custom_observer_settings <- c("measure_col", "baseline--value_col","analysisFlag--value_col") - - - #Establish observers to update settings UI for all inputs - # Different observers: - # (1a) update UI based on data selection & original settings object - # - dependent on: colnames() - # - populate all UI inputs - # - flag required settings - # (1b) Do 1a for the custom settings (e.g. measure_values options). These contained nested observers - # - dependent on: parent input$xxx - # (2) append status messages to UI - # - after UI is filled, we generate a NEW settings object & status - # - dependent on: the new settings/status, which will update after every user selection - - - # observeEvent(data(), { - observe({ - req(colnames()) - - for (name in isolate(input_names())){ - #print(name) - setting_key <- as.list(strsplit(name,"\\-\\-")) - setting_value <- safetyGraphics:::getSettingValue(key=setting_key, settings=settings()) - setting_label <- safetyGraphics:::getSettingsMetadata(charts="eDiSH", text_keys=name, cols="label") - setting_description <- safetyGraphics:::getSettingsMetadata(charts="eDiSH", text_keys=name, cols="description") - - - column_mapping_ids <- safetyGraphics:::getSettingsMetadata(charts="eDiSH") %>% filter(column_mapping==TRUE) %>% pull(text_key) - - - if (name %in% column_mapping_ids){ - sortedChoices<-NULL - if(is.null(setting_value)){ - sortedChoices<-colnames() - updateSelectizeInput(session, name, choices=sortedChoices, - options = list( - onInitialize = I('function() { - //console.log("initializing input w/o value") - //console.log(this) - this.setValue(""); - }') - )) - - - }else{ - sortedChoices<-unique(c(setting_value, colnames())) - updateSelectizeInput(session, name, choices=sortedChoices, - options = list (onInitialize = I('function() { - //console.log("initializing input with value") - //console.log(this) - }') - )) - - } - } - - # 2. Check for custom observers and initialize if needed - if(name %in% custom_observer_settings){ - runCustomObserver(name=name) - } - - # 3. label setting - labelSetting(ns=ns, name=name, label=setting_label, description=setting_description) - - # 4. Flag the input if it is required - if(name %in% req_settings){ - flagSetting(session=session, name=name, originalLabel=setting_label) - - } - } - }) - - - observe({ - for (name in isolate(input_names())){ - - # 5. Print a warning if the input failed a validation check - if(name %in% status_df()$text_key){ - - status_short <- status_df()[status_df()$text_key==name, "message_short"] - status_long <- status_df()[status_df()$text_key==name, "message_long"] - - updateSettingStatus(ns=ns, name=name, status_short=status_short, status_long=status_long) - } - - } - }) - + ###################################################################### + # print validation messages + ###################################################################### + observe({ + for (key in isolate(input_names())){ + + if(key %in% status_df()$text_key){ + + status_short <- status_df()[status_df()$text_key==key, "message_short"] + status_long <- status_df()[status_df()$text_key==key, "message_long"] + icon <- status_df()[status_df()$text_key==key, "icon"] + updateSettingStatus(ns=ns, key=key, status_short=status_short, status_long=status_long, icon=icon) + } + + } + }) + ### return updated settings and status to global env. - return(list(settings = reactive(settings_new()), + return(list(charts = reactive(input$charts), + settings = reactive(settings_new()), status = reactive(status_new()))) } diff --git a/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R b/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R index 0738d245..83a3f4c3 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R @@ -1,230 +1,23 @@ renderSettingsUI <- function(id){ - ns <- NS(id) - tagList( - verticalLayout( - - fluidRow( - column(6, - div( - div(style="display: inline-block;", h3("Data Mapping")), - div(style="display: inline-block;", checkboxInput(ns("show_data_mapping"), "show", TRUE)) - ) + fluidRow( + column(12, + class="chartSelect section", + checkboxGroupInput( + ns("charts"), + "Select Chart(s):", + choices = c("e-DISH" = "edish"), + selected="edish" ) - ), - conditionalPanel(condition="input.show_data_mapping", ns=ns, - fluidRow( - column(4, - wellPanel( - div( - span(id = ns("tt_lbl_id_col"), title = "", - tags$label(id = ns("lbl_id_col"), "")), - span(id = ns("tt_msg_id_col"), title = "", - tags$label(id = ns("msg_id_col"), "")), - selectizeInput(ns("id_col"),NULL, choices = NULL) - - ), - - div( - span(id = ns("tt_lbl_value_col"), title = "", - tags$label(id = ns("lbl_value_col"), "")), - span(id = ns("tt_msg_value_col"), title = "", - tags$label(id = ns("msg_value_col"), "")), - selectizeInput(ns("value_col"),NULL, choices = NULL) - - ), - - div( - span(id = ns("tt_lbl_measure_col"), title = "", - tags$label(id = ns("lbl_measure_col"), "")), - span(id = ns("tt_msg_measure_col"), title = "", - tags$label(id = ns("msg_measure_col"), "")), - selectizeInput(ns("measure_col"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_measure_values--ALT"), title = "", - tags$label(id = ns("lbl_measure_values--ALT"), "")), - span(id = ns("tt_msg_measure_values--ALT"), title = "", - tags$label(id = ns("msg_measure_values--ALT"), "")), - selectizeInput(ns("measure_values--ALT"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_measure_values--AST"), title = "", - tags$label(id = ns("lbl_measure_values--AST"), "")), - span(id = ns("tt_msg_measure_values--AST"), title = "", - tags$label(id = ns("msg_measure_values--AST"), "")), - selectizeInput(ns("measure_values--AST"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_measure_values--TB"), title = "", - tags$label(id = ns("lbl_measure_values--TB"), "")), - span(id = ns("tt_msg_measure_values--TB"), title = "", - tags$label(id = ns("msg_measure_values--TB"), "")), - selectizeInput(ns("measure_values--TB"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_measure_values--ALP"), title = "", - tags$label(id = ns("lbl_measure_values--ALP"), "")), - span(id = ns("tt_msg_measure_values--ALP"), title = "", - tags$label(id = ns("msg_measure_values--ALP"), "")), - selectizeInput(ns("measure_values--ALP"),NULL, choices = NULL) - ), - # ), - div( - span(id = ns("tt_lbl_normal_col_low"), title = "", - tags$label(id = ns("lbl_normal_col_low"), "")), - span(id = ns("tt_msg_normal_col_low"), title = "", - tags$label(id = ns("msg_normal_col_low"), "")), - selectizeInput(ns("normal_col_low"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_normal_col_high"), title = "", - tags$label(id = ns("lbl_normal_col_high"), "")), - span(id = ns("tt_msg_normal_col_high"), title = "", - tags$label(id = ns("msg_normal_col_high"), "")), - selectizeInput(ns("normal_col_high"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_visit_col"), title = "", - tags$label(id = ns("lbl_visit_col"), "")), - span(id = ns("tt_msg_visit_col"), title = "", - tags$label(id = ns("msg_visit_col"), "")), - selectizeInput(ns("visit_col"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_visitn_col"), title = "", - tags$label(id = ns("lbl_visitn_col"), "")), - span(id = ns("tt_msg_visitn_col"), title = "", - tags$label(id = ns("msg_visitn_col"), "")), - selectizeInput(ns("visitn_col"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_studyday_col"), title = "", - tags$label(id = ns("lbl_studyday_col"), "")), - span(id = ns("tt_msg_studyday_col"), title = "", - tags$label(id = ns("msg_studyday_col"), "")), - selectizeInput(ns("studyday_col"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_baseline--value_col"), title = "", - tags$label(id = ns("lbl_baseline--value_col"), "")), - span(id = ns("tt_msg_baseline--value_col"), title = "", - tags$label(id = ns("msg_baseline--value_col"), "")), - selectizeInput(ns("baseline--value_col"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_baseline--values"), title = "", - tags$label(id = ns("lbl_baseline--values"), "")), - span(id = ns("tt_msg_baseline--values"), title = "", - tags$label(id = ns("msg_baseline--values"), "")), - selectizeInput(ns("baseline--values"),NULL, choices = NULL, multiple = TRUE) - ), - div( - span(id = ns("tt_lbl_filters"), title = "", - tags$label(id = ns("lbl_filters"), "")), - span(id = ns("tt_msg_filters"), title = "", - tags$label(id = ns("msg_filters"), "")), - selectInput(ns("filters"),NULL, choices = NULL, selected = NULL, multiple = TRUE) - ), - div( - span(id = ns("tt_lbl_group_cols"), title = "", - tags$label(id = ns("lbl_group_cols"), "")), - span(id = ns("tt_msg_group_cols"), title = "", - tags$label(id = ns("msg_group_cols"), "")), - selectInput(ns("group_cols"),NULL, choices = NULL, selected = NULL, multiple = TRUE) - ), - div( - span(id = ns("tt_lbl_analysisFlag--value_col"), title = "", - tags$label(id = ns("lbl_analysisFlag--value_col"), "")), - span(id = ns("tt_msg_analysisFlag--value_col"), title = "", - tags$label(id = ns("msg_analysisFlag--value_col"), "")), - selectizeInput(ns("analysisFlag--value_col"),NULL, choices = NULL) - ), - div( - span(id = ns("tt_lbl_analysisFlag--values"), title = "", - tags$label(id = ns("lbl_analysisFlag--values"), "")), - span(id = ns("tt_msg_analysisFlag--values"), title = "", - tags$label(id = ns("msg_analysisFlag--values"), "")), - selectizeInput(ns("analysisFlag--values"),NULL, choices = NULL, multiple = TRUE) - ) - )) - - ) - ), - - fluidRow( - column(6, - div( - div(style="display: inline-block;", h3("Measure Settings")), - div(style="display: inline-block;", checkboxInput(ns("show_measure_settings"), "show", TRUE)) - ) - ) - ), - conditionalPanel(condition="input.show_measure_settings", ns=ns, - fluidRow( - column(4, - wellPanel( - div( - div(id = ns("tt_lbl_x_options"), title = "", - tags$label(id = ns("lbl_x_options"), "")), - selectizeInput(ns("x_options"),NULL, choices = c("ALT", "AST", "ALP","TB"), selected = c("ALT", "AST", "ALP"), multiple=TRUE) - ), - div( - div(id = ns("tt_lbl_y_options"), title = "", - tags$label(id = ns("lbl_y_options"), "")), - selectizeInput(ns("y_options"),NULL, choices = c("ALT", "AST", "ALP","TB"), selected = c("TB"), multiple = TRUE) - ) - - ) - ) - ) - ), - fluidRow( - column(6, - div( - div(style="display: inline-block;", h3("Appearance Settings")), - div(style="display: inline-block;", checkboxInput(ns("show_appearance_settings"), "show", TRUE)) - ) - ) - ), - conditionalPanel(condition="input.show_appearance_settings", ns=ns, - fluidRow( - column(4, - wellPanel( - div( - div(id = ns("tt_lbl_visit_window"), title = "", - tags$label(id = ns("lbl_visit_window"), "")), - sliderInput(ns("visit_window"),NULL, value = 30, min=0, max=50) - ), - div( - div(id = ns("tt_lbl_r_ratio_filter"), title = "", - tags$label(id = ns("lbl_r_ratio_filter"), "")), - checkboxInput(ns("r_ratio_filter"),NULL, value = TRUE) - ), - conditionalPanel( - condition="input.r_ratio_filter==true", ns=ns, - div( - div(id = ns("tt_lbl_r_ratio_cut"), title = "", - tags$label(id = ns("lbl_r_ratio_cut"), "")), - sliderInput(ns("r_ratio_cut"),NULL, value = 0, min=0, max =1) - ) - ), - div( - div(id = ns("tt_lbl_showTitle"), title = "", - tags$label(id = ns("lbl_showTitle"), "")), - checkboxInput(ns("showTitle"),NULL, value = TRUE) - ), - div( - div(id = ns("tt_lbl_warningText"), title = "", - tags$label(id = ns("lbl_warningText"), "")), - textAreaInput (ns("warningText"),NULL, rows =4, - value = "Caution: This interactive graphic is not validated. Any clinical recommendations based on this tool should be confirmed using your organizations standard operating procedures.") - ) - ) - ) - ) ) - )) + ), + #TODO - make this a loop based on metadata + fluidRow( + createSettingsSection("data_mapping", "Data Mappings",6,ns), + createSettingsSection("measure_settings", "Measure Settings",6,ns), + createSettingsSection("appearance_settings", "Appearance Settings",6,ns) + ) + ) } diff --git a/inst/eDISH_app/modules/renderSettings/util/createControl.R b/inst/eDISH_app/modules/renderSettings/util/createControl.R new file mode 100644 index 00000000..11b418a2 --- /dev/null +++ b/inst/eDISH_app/modules/renderSettings/util/createControl.R @@ -0,0 +1,90 @@ +#' Create setting control +#' +#' Workflow: +#' (1) Get setting label and description from metadata +#' (2) Get setting value from settings object +#' (3) Get choices and placeholder text for the selectors based on metadata, data, and settings +#' (4) Create HTML code for the selector based on the following metadata: +#' - whether the option is a column or field-level input +#' - data type of the setting (e.g. character/numeric/logical, vector of length 1 vs >1) +#' - label, description, choices, selected value, placeholder text +#' +#' @param key A character key representing the setting of interest +#' @param metadata Metadata data frame to be queried for information about the setting +#' @param data A data frame to be used to populate control options +#' @param settings A settings list to be used to populate control options +#' @param ns The namespace of the current module +#' +#' @return HTML code for the div containing the setting of interest +createControl <- function(key, metadata, data, settings, ns){ + + sm_key <- filter(metadata, text_key==key) + ctl_id <- paste0("ctl_", key) + tt_msg <- paste0("tt_msg_", key) + msg <- paste0("msg_", key) + + ### get metadata for the input + setting_key <- as.list(strsplit(key,"\\-\\-")) + setting_value <- safetyGraphics:::getSettingValue(key=setting_key, settings=settings) + setting_label <- createSettingLabel(key) + setting_description <- getSettingsMetadata(text_keys=key, cols="description") + setting_required <- ifelse(getSettingsMetadata(text_keys=key, cols="setting_required"),"\nSetting Required","\nSetting Optional") + + ### if a field-level input, get metadata about the parent column-level input + field_column <- NULL + field_column_label <- NULL + if (!is.null(sm_key$field_column_key)){ + field_column <- safetyGraphics:::getSettingValue(key=list(sm_key$field_column_key), settings=settings) + field_column_label <- getSettingsMetadata(text_key = sm_key$field_column_key, cols = "label") + } + + ### get the choices for the selectors + value <- NULL + choices <- NULL + placeholder <- NULL + + if(sm_key$column_mapping==TRUE & is.null(setting_value)){ #column mapping - no value specified + choices <- colnames(data) + placeholder <- list(onInitialize = I('function() {this.setValue("");}')) + } else if(sm_key$column_mapping==TRUE & !is.null(setting_value)) { #column mapping - value specified + choices <- unique(c(setting_value, colnames(data))) + placeholder <- list (onInitialize = I('function() { }')) + } else if (sm_key$field_mapping==TRUE & is.null(field_column)){ ## if there is NOT a column specified in settings + placeholder <- list( + placeholder = paste0("Please select a ", field_column_label), + onInitialize = I('function() { + this.setValue("");}') + ) + } else if (sm_key$field_mapping==TRUE & !is.null(field_column)){ ## if there is NOT a column specified in settings + choices <- unique(c(setting_value, as.character(data[,field_column]))) %>% unlist + placeholder <- list (onInitialize = I('function() { }')) + } else if (sm_key$setting_type=="vector"){ + choices <- setting_value ### this is meant to cover the scenario for x_options/y_options + } + + ### create code for the UI + multiple <- (sm_key$setting_type=="vector") + + if (sm_key$column_mapping==TRUE | sm_key$field_mapping==TRUE){ + input <- selectizeInput(inputId = ns(key), label = NULL, choices = choices, options = placeholder, multiple = multiple) + } else if (sm_key$setting_type=="vector"){ + input <- selectizeInput(inputId = ns(key), label = NULL, choices = choices, selected = choices, multiple = TRUE) + } else if (sm_key$setting_type=="numeric"){ + input <- sliderInput(inputId = ns(key), label = NULL, value=setting_value, min=0, max=50) + } else if (sm_key$setting_type=="logical"){ + input <- checkboxInput(inputId = ns(key), label = NULL, value=setting_value) + } else if (sm_key$setting_type=="character"){ + input <-textAreaInput(inputId = ns(key), label = NULL, value = setting_value) + } + + div( + class="control-wrap", + id=ns(ctl_id), + span(title = paste0(setting_description," ",setting_required), tags$label(HTML(setting_label))), + div( + class="select-wrap", + input, + div(id = ns(tt_msg), title = "", tags$label(id = ns(msg), ""), class="status") + ) + ) +} \ No newline at end of file diff --git a/inst/eDISH_app/modules/renderSettings/util/createSettingLabel.R b/inst/eDISH_app/modules/renderSettings/util/createSettingLabel.R new file mode 100644 index 00000000..5f162221 --- /dev/null +++ b/inst/eDISH_app/modules/renderSettings/util/createSettingLabel.R @@ -0,0 +1,13 @@ +#' Create label for chart setting selector +#' +#' @param key A character key representing the setting of interest. +#' +#' @return A character string containing full HTML text to be used for input label. Contains info icon to +#' indicate that description is available upon mouseover, setting label, and asterisk if setting is required. +#' +createSettingLabel <- function(key){ + sm <- getSettingsMetadata(text_keys=key) + setting_label <- sm$label + required <- sm$setting_required + paste0(setting_label)#, " " ) +} diff --git a/inst/eDISH_app/modules/renderSettings/util/createSettingsSection.R b/inst/eDISH_app/modules/renderSettings/util/createSettingsSection.R new file mode 100644 index 00000000..eca0959e --- /dev/null +++ b/inst/eDISH_app/modules/renderSettings/util/createSettingsSection.R @@ -0,0 +1,24 @@ +createSettingsSection <- function(class, label,cols,ns){ + section <- + column(cols, + wellPanel( + class=paste0(class," section"), + h3( + label, + materialSwitch( + ns(paste0("show_",class)), + label = "", + right=TRUE, + value = TRUE, + status = "primary" + ) + ), + conditionalPanel( + condition=paste0("input.show_",class), + ns=ns, + uiOutput(ns(paste0(class,"_ui"))) + ) + ) + ) + return(section) +} \ No newline at end of file diff --git a/inst/eDISH_app/modules/renderSettings/util/createSettingsUI.R b/inst/eDISH_app/modules/renderSettings/util/createSettingsUI.R new file mode 100644 index 00000000..086a5940 --- /dev/null +++ b/inst/eDISH_app/modules/renderSettings/util/createSettingsUI.R @@ -0,0 +1,22 @@ +#' Create UI for specified section of settings tab +#' +#' @param data A data frame to be used to populate control options +#' @param settings A settings list to be used to populate control options +#' @param setting_cat_val Settings category. One of "data","measure","appearance" +#' @param charts A character vector containing names of charts of interest +#' @param ns The namespace of the current module +#' +#' @return A list containing the UI code for all selectors in the specified settings category. +createSettingsUI <- function(data, settings, setting_cat_val, charts, ns){ + + sm <- getSettingsMetadata(charts=charts) %>% + filter(setting_cat==setting_cat_val) + + lapply(sm$text_key, function(key){ + createControl(key, metadata = sm, data, settings, ns) + }) +} + + + + diff --git a/inst/eDISH_app/modules/renderSettings/util/flagSetting.R b/inst/eDISH_app/modules/renderSettings/util/flagSetting.R deleted file mode 100644 index eb0b6542..00000000 --- a/inst/eDISH_app/modules/renderSettings/util/flagSetting.R +++ /dev/null @@ -1,8 +0,0 @@ -flagSetting<-function(session, name, originalLabel){ - - originalLabel <- paste("", originalLabel) - - shinyjs::html(id = paste0("lbl_", name), - html = paste0(originalLabel, "*"), - add = FALSE) -} diff --git a/inst/eDISH_app/modules/renderSettings/util/labelSetting.R b/inst/eDISH_app/modules/renderSettings/util/labelSetting.R deleted file mode 100644 index cf0f4cf7..00000000 --- a/inst/eDISH_app/modules/renderSettings/util/labelSetting.R +++ /dev/null @@ -1,14 +0,0 @@ -labelSetting<-function(ns, name, label, description){ - - - label <- paste("", label) - - label_id <- paste0("lbl_", name) - shinyjs::html(id = label_id, - html = label, - add = FALSE) - - tooltip_id <- paste0("tt_lbl_", name) - - shinyjs::runjs(paste0('$("#',ns(tooltip_id), '").attr("title", "', description, '")')) -} \ No newline at end of file diff --git a/inst/eDISH_app/modules/renderSettings/util/updateSettingStatus.R b/inst/eDISH_app/modules/renderSettings/util/updateSettingStatus.R index c594d42b..a3003b57 100644 --- a/inst/eDISH_app/modules/renderSettings/util/updateSettingStatus.R +++ b/inst/eDISH_app/modules/renderSettings/util/updateSettingStatus.R @@ -1,21 +1,29 @@ -updateSettingStatus<-function(ns, name, status_short, status_long){ - - msg_id <- paste0("msg_", name) - tooltip_id <- paste0("tt_msg_", name) +#' Update setting validation status +#' +#' Workflow: +#' (1) Update abbreviated status for a given setting using green (valid) or red (invalid) text +#' (2) Update long status message for a given setting to be displayed upon mouseover +#' +#' @param ns The namespace of the current module +#' @param key A character key representing the setting of interest +#' @param status_short Abbreviated validation message +#' @param status_long Detailed validation message + +updateSettingStatus<-function(ns, key, status_short, status_long, icon){ - if (status_short=="OK"){ - shinyjs::html(id = msg_id, - html = paste(" ", status_short,"", - "")) - - shinyjs::runjs(paste0('$("#',ns(tooltip_id), '").attr("title", "Selection is valid")')) - - } else { - shinyjs::html(id = msg_id, - html = paste(" ", status_short,"", - "")) - - shinyjs::runjs(paste0('$("#',ns(tooltip_id), '").attr("title", "', status_long, '")')) + ctl_id<-paste0("ctl_", key) + #TODO: get msg_ and tooltip_ selectors using relative position to control id + msg_id <- paste0("msg_", key) + tooltip_id <- paste0("tt_msg_", key) + if(status_short=="OK"){ + shinyjs::addClass(id=ctl_id, class="valid") + shinyjs::removeClass(id=ctl_id, class="invalid") + }else{ + shinyjs::removeClass(id=ctl_id, class="valid") + shinyjs::addClass(id=ctl_id, class="invalid") + } + shinyjs::html(id = msg_id, html = paste(icon)) + if(nchar(status_long)>0){ + shinyjs::runjs(paste0('$("#',ns(tooltip_id), '").attr("title", "',status_long,'").addClass("details")')) } - } diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index e18fc851..76dfae74 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -1,3 +1,10 @@ +# Server code for safetyGraphics App +# - calls dataUpload module (data tab) +# - calls renderSettings module (settings tab) +# - calls renderEDishChart (chart tab) +# - uses render UI to append a red X or green check on tab title, +# indicating whether user has satisfied requirements of that tab + function(input, output, session){ @@ -9,7 +16,8 @@ function(input, output, session){ # add status to data panel nav bar # always OK for now, since example data is loaded by default output$data_tab_title = renderUI({ - HTML(paste("Data", icon("check", class="ok"))) + # HTML(paste("Data", icon("check", class="ok"))) + span(tagList("Data", icon("check", class="ok"))) }) # based on selected data set & generated/selected settings obj, generate settings page. @@ -20,7 +28,8 @@ function(input, output, session){ # # reutrns updated settings and validation status settings_new <- callModule(renderSettings, "settingsUI", - data = isolate(reactive(dataUpload_out$data_selected())), + # data = isolate(reactive(dataUpload_out$data_selected())), # this doesnt make sense + data = reactive(dataUpload_out$data_selected()), settings = reactive(dataUpload_out$settings()), status = reactive(dataUpload_out$status())) @@ -33,7 +42,7 @@ function(input, output, session){ HTML(paste("Settings", icon("times", class="notok"))) } }) - + # update charts navbar output$chart_tab_title = renderUI({ if (settings_new$status()$valid==TRUE){ @@ -42,8 +51,8 @@ function(input, output, session){ HTML(paste("Chart", icon("times", class="notok"))) } }) - - + + # module to render eDish chart callModule(renderEDishChart, "chartEDish", data = reactive(dataUpload_out$data_selected()), diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index a6e2eea2..27aa19ee 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -1,8 +1,10 @@ +# UI Code for safetyGraphics App + tagList( useShinyjs(), - tags$style(HTML(" - .ok { color:#008000;} - .notok {color: #FF0000;}")), + tags$head( + tags$link(rel = "stylesheet", type = "text/css", href = "index.css") + ), navbarPage("eDISH Shiny app", tabPanel(title = htmlOutput("data_tab_title"), dataUploadUI("datatab") diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css new file mode 100644 index 00000000..1f292331 --- /dev/null +++ b/inst/eDISH_app/www/index.css @@ -0,0 +1,81 @@ +/* --- hide the chartSelect div until we're ready to implement multiple charts --- */ +.chartSelect{ + display:none; +} +/* ------------------------------------------------------------------------------- */ + +.section{ + min-width:400px; +} + +.control-wrap{ + margin-top:1em; +} + +.control-wrap span label{ + font-weight:normal; + color:#444; + cursor:help; +} + +.control-wrap .select-wrap .form-group{ + width:90%; /* TODO: don't love this ... update eventually */ + display:inline-block; + margin-bottom:0; +} + +.control-wrap .select-wrap .form-group .form-control{ + margin-bottom:0; +} + +.control-wrap .select-wrap .status{ + display:inline-block; + margin-top:.5em; + padding-left:0.5em; + vertical-align:top; +} + +.control-wrap .select-wrap .status.details label i { + cursor:help; +} + + +/* Validation Coloring */ + +.control-wrap.valid .select-wrap .status{ + color:green; +} + +.control-wrap.valid .select-wrap div .selectize-control .selectize-input{ + border-color:green; +} + +.control-wrap.invalid .select-wrap .status{ + color:red; +} + +.control-wrap.invalid .select-wrap div .selectize-control .selectize-input{ + border-color:red; +} + +/* Settings - header tweaks */ +.section h3 { + margin:0; +} + +.section h3 .form-group { + display:inline; +} + +.section h3 .form-group .material-switch{ + float:right; +} + + +.ok { + color:#008000; +} + +.notok { + color: #FF0000; +} \ No newline at end of file diff --git a/man/detectStandard.Rd b/man/detectStandard.Rd index 8536045e..ec561f35 100644 --- a/man/detectStandard.Rd +++ b/man/detectStandard.Rd @@ -14,16 +14,16 @@ detectStandard(data, includeFields = TRUE, domain = "labs") \item{domain}{The data domain for the data set provided. Default: \code{"labs"}.} } \value{ -A list containing the matching \code{"standard"} ("ADaM", "SDTM" or "None") and a list of \code{"details"} describing each standard considered. +A list containing the matching \code{"standard"} from \code{"standardMetadata"} and a list of \code{"details"} describing each standard considered. } \description{ -This function attempts to detect the data CDISC clinical standard used in a given R data frame. +This function attempts to detect the clinical data standard used in a given R data frame. } \details{ -This function compares the columns in the provided \code{"data"} with the required columns for a given data standard/domain combination. The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, "labs" is the only domain supported. +This function compares the columns in the provided \code{"data"} with the required columns for a given data standard/domain combination. The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data by default. Additional standards can be added by modifying the \code{"standardMetadata"} data set included as part of this package. Currently, "labs" is the only domain supported. } \examples{ -detectStandard(adlbc)[["standard"]] #AdAM +detectStandard(adlbc)[["standard"]] #adam detectStandard(iris)[["standard"]] #none \dontrun{ diff --git a/man/evaluateStandard.Rd b/man/evaluateStandard.Rd index ac1a38ab..ecbcde34 100644 --- a/man/evaluateStandard.Rd +++ b/man/evaluateStandard.Rd @@ -16,7 +16,7 @@ evaluateStandard(data, standard, includeFields = TRUE, domain = "labs") \item{domain}{data domain. "labs" only for now.} } \value{ -a list describing to what degree the data set matches the data standard. The "match" property describes compliance with the standard as "Full", "Partial" or "None". The "checks" property is a list of the data elements expected for the standard and whether they are "valid" in the given data set. "valid_checks" and "invalid_checks" provide counts of the specified checks. +a list describing to what degree the data set matches the data standard. The "match" property describes compliance with the standard as "full", "partial" or "none". The "checks" property is a list of the data elements expected for the standard and whether they are "valid" in the given data set. "total_checks", "valid_checks" and "invalid_checks" provide counts of the specified checks. "match_percent" is calculated as valid_checks/total_checks. } \description{ Determines whether the required data elements in a data standard are found in a given data frame diff --git a/man/getSettingsMetadata.Rd b/man/getSettingsMetadata.Rd index 78809723..ac090a96 100644 --- a/man/getSettingsMetadata.Rd +++ b/man/getSettingsMetadata.Rd @@ -5,7 +5,8 @@ \title{Get metadata about chart settings} \usage{ getSettingsMetadata(charts = NULL, text_keys = NULL, cols = NULL, - filter_expr = NULL, metadata = safetyGraphics::settingsMetadata) + filter_expr = NULL, add_standards = TRUE, + metadata = safetyGraphics::settingsMetadata) } \arguments{ \item{charts}{optional vector of chart names used to filter the metadata. Exact matches only (case-insensitive). All rows returned by default.} @@ -16,6 +17,8 @@ getSettingsMetadata(charts = NULL, text_keys = NULL, cols = NULL, \item{filter_expr}{optional filter expression used to subset the data.} +\item{add_standards}{should data standard info stored in standardsMetadata be included} + \item{metadata}{metadata data frame to be queried} } \value{ diff --git a/man/safetyGraphicsApp.Rd b/man/safetyGraphicsApp.Rd index 6f75fba1..f6ed62e0 100644 --- a/man/safetyGraphicsApp.Rd +++ b/man/safetyGraphicsApp.Rd @@ -4,7 +4,7 @@ \alias{safetyGraphicsApp} \title{Run the interactive safety graphics builder} \usage{ -safetyGraphicsApp(maxFileSize = 20) +safetyGraphicsApp(maxFileSize = NULL) } \arguments{ \item{maxFileSize}{maximum file size in MB allowed for file upload.} diff --git a/man/settingsMetadata.Rd b/man/settingsMetadata.Rd index fa44dcd2..7574fe69 100644 --- a/man/settingsMetadata.Rd +++ b/man/settingsMetadata.Rd @@ -16,8 +16,7 @@ \item{column_type}{Expected type for the data column values. Should be "character","logical" or "numeric"} \item{field_mapping}{Flag indicating whether the setting corresponds to a field-level mapping in the data} \item{field_column_key}{Key for the column that provides options for the field-level mapping in the data} - \item{adam}{Settings values for the ADaM standard} - \item{sdtm}{Settings values for the SDTM standard} + \item{setting_cat}{Setting category (data, measure, appearance)} }} \source{ Created for this package diff --git a/man/standardsMetadata.Rd b/man/standardsMetadata.Rd new file mode 100644 index 00000000..601a48c0 --- /dev/null +++ b/man/standardsMetadata.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/standardsMetadata.R +\docType{data} +\name{standardsMetadata} +\alias{standardsMetadata} +\title{Standards Metadata} +\format{A data frame with 25 rows and 3 columns +\describe{ + \item{text_key}{Text key indicating the setting name. \code{'--'} delimiter indicates a nested setting} + \item{adam}{Settings values for the ADaM standard} + \item{sdtm}{Settings values for the SDTM standard} +}} +\source{ +Created for this package +} +\usage{ +standardsMetadata +} +\description{ +Metadata about the data standards used to configure safetyGraphics charts. One record per unique setting. Columns contain default setting values for clinical data standards, like the CDISC "adam" and "sdtm" standards. +} +\keyword{datasets} diff --git a/tests/testthat/test_detectStandard.R b/tests/testthat/test_detectStandard.R index 687e71c7..931500b4 100644 --- a/tests/testthat/test_detectStandard.R +++ b/tests/testthat/test_detectStandard.R @@ -5,53 +5,54 @@ test_that("a list with the expected properties and structure is returned",{ a<- detectStandard(data.frame()) expect_is(a,"list") - expect_named(a,c("details","standard")) + expect_named(a,c("details","standard","standard_percent")) expect_is(a[["standard"]],"character") - expect_match(a[["standard"]],"SDTM|ADaM|None") + expect_match(a[["standard"]],"sdtm|adam|none") expect_is(a[["details"]],"list") - expect_named(a[["details"]],c("ADaM","SDTM")) + expect_named(a[["details"]],c("sdtm","adam")) + expect_equal(a[["standard_percent"]],0) }) test_that("correct standards are identified",{ - expect_equal(detectStandard(adlbc)[["standard"]],"ADaM") - expect_equal(detectStandard(adlbc)[["details"]][["ADaM"]][["match"]], "Full") - expect_equal(detectStandard(adlbc)[["details"]][["SDTM"]][["match"]], "Partial") + expect_equal(detectStandard(adlbc)[["standard"]],"adam") + expect_equal(detectStandard(adlbc)[["details"]][["adam"]][["match"]], "full") + expect_equal(detectStandard(adlbc)[["details"]][["sdtm"]][["match"]], "partial") adam_params <- c("Alanine Aminotransferase (U/L)","Aspartate Aminotransferase (U/L)","Bilirubin (umol/L)","Alkaline Phosphatase (U/L)") adam_test_data<-data.frame(USUBJID="001",AVAL=10,PARAM=adam_params, VISIT="Visit 1",VISITNUM=1,ADY=0,A1LO=0,A1HI=20) - expect_equal(detectStandard(adam_test_data)[["standard"]],"ADaM") - expect_equal(detectStandard(adam_test_data)[["details"]][["ADaM"]][["match"]], "Full") - expect_equal(detectStandard(adam_test_data)[["details"]][["SDTM"]][["match"]], "Partial") + expect_equal(detectStandard(adam_test_data)[["standard"]],"adam") + expect_equal(detectStandard(adam_test_data)[["details"]][["adam"]][["match"]], "full") + expect_equal(detectStandard(adam_test_data)[["details"]][["sdtm"]][["match"]], "partial") sdtm_params<-c("Aminotransferase, alanine (ALT)","Aminotransferase, aspartate (AST)","Total Bilirubin","Alkaline phosphatase (ALP)") sdtm_test_data<-data.frame(USUBJID="001",STRESN=10,TEST=sdtm_params,VISIT="Visit 1",VISITNUM=1,DY=0,STNRLO=0,STNRHI=20) - expect_equal(detectStandard(sdtm_test_data)[["standard"]],"SDTM") - expect_equal(detectStandard(sdtm_test_data)[["details"]][["ADaM"]][["match"]], "Partial") - expect_equal(detectStandard(sdtm_test_data)[["details"]][["SDTM"]][["match"]], "Full") + expect_equal(detectStandard(sdtm_test_data)[["standard"]],"sdtm") + expect_equal(detectStandard(sdtm_test_data)[["details"]][["adam"]][["match"]], "partial") + expect_equal(detectStandard(sdtm_test_data)[["details"]][["sdtm"]][["match"]], "full") empty_test_data<-data.frame("") - expect_equal(detectStandard(empty_test_data)[["standard"]],"None") - expect_equal(detectStandard(empty_test_data)[["details"]][["ADaM"]][["match"]], "None") - expect_equal(detectStandard(empty_test_data)[["details"]][["SDTM"]][["match"]], "None") + expect_equal(detectStandard(empty_test_data)[["standard"]],"none") + expect_equal(detectStandard(empty_test_data)[["details"]][["adam"]][["match"]], "none") + expect_equal(detectStandard(empty_test_data)[["details"]][["sdtm"]][["match"]], "none") case_sensitive_test_data<-data.frame(usubjid="001",AVAL=10,PARAM=adam_params, VISIT="Visit 1",VISITNUM=1,ADY=0,A1LO=0,A1HI=20) - expect_equal(detectStandard(case_sensitive_test_data)[["standard"]],"ADaM") - expect_equal(detectStandard(case_sensitive_test_data)[["details"]][["ADaM"]][["match"]], "Full") - expect_equal(detectStandard(case_sensitive_test_data)[["details"]][["SDTM"]][["match"]], "Partial") + expect_equal(detectStandard(case_sensitive_test_data)[["standard"]],"adam") + expect_equal(detectStandard(case_sensitive_test_data)[["details"]][["adam"]][["match"]], "full") + expect_equal(detectStandard(case_sensitive_test_data)[["details"]][["sdtm"]][["match"]], "partial") - #NOTE: SDTM takes precedence over ADAM + #NOTE: sdtm takes precedence over adam sdtm_and_adam_test_data<-cbind(adam_test_data,sdtm_test_data) - expect_equal(detectStandard(sdtm_and_adam_test_data)[["standard"]],"SDTM") - expect_equal(detectStandard(sdtm_and_adam_test_data)[["details"]][["ADaM"]][["match"]], "Full") - expect_equal(detectStandard(sdtm_and_adam_test_data)[["details"]][["SDTM"]][["match"]], "Full") + expect_equal(detectStandard(sdtm_and_adam_test_data)[["standard"]],"sdtm") + expect_equal(detectStandard(sdtm_and_adam_test_data)[["details"]][["adam"]][["match"]], "full") + expect_equal(detectStandard(sdtm_and_adam_test_data)[["details"]][["sdtm"]][["match"]], "full") - #NOTE: SDTM takes precedence over ADAM in partial matches as well + #NOTE: sdtm takes precedence over adam in partial matches as well sdtm_and_adam_partial_test_data<-data.frame(USUBJID="001",VISIT="Visit 1") - expect_equal(detectStandard(sdtm_and_adam_partial_test_data)[["standard"]],"SDTM") - expect_equal(detectStandard(sdtm_and_adam_partial_test_data)[["details"]][["ADaM"]][["match"]],"Partial") - expect_equal(detectStandard(sdtm_and_adam_partial_test_data)[["details"]][["SDTM"]][["match"]],"Partial") + expect_equal(detectStandard(sdtm_and_adam_partial_test_data)[["standard"]],"sdtm") + expect_equal(detectStandard(sdtm_and_adam_partial_test_data)[["details"]][["adam"]][["match"]],"partial") + expect_equal(detectStandard(sdtm_and_adam_partial_test_data)[["details"]][["sdtm"]][["match"]],"partial") }) diff --git a/tests/testthat/test_evaluateStandard.R b/tests/testthat/test_evaluateStandard.R index 72dd5ec8..352fe5e6 100644 --- a/tests/testthat/test_evaluateStandard.R +++ b/tests/testthat/test_evaluateStandard.R @@ -2,16 +2,16 @@ context("Tests for the evaluateStandard() function") library(safetyGraphics) test_that("basic test cases evaluate as expected",{ - expect_equal(evaluateStandard(data=adlbc, standard="adam")[["match"]],"Full") - expect_equal(evaluateStandard(data=adlbc, standard="sdtm")[["match"]],"Partial") - expect_equal(evaluateStandard(data=data.frame(), standard="sdtm")[["match"]],"None") + expect_equal(evaluateStandard(data=adlbc, standard="adam")[["match"]],"full") + expect_equal(evaluateStandard(data=adlbc, standard="sdtm")[["match"]],"partial") + expect_equal(evaluateStandard(data=data.frame(), standard="sdtm")[["match"]],"none") }) test_that("a list with the expected properties and structure is returned",{ a<- evaluateStandard(data=data.frame(),standard="adam") expect_is(a,"list") - expect_named(a,c('standard', 'checks', 'valid_count', 'invalid_count', 'match')) + expect_named(a,c('standard', 'checks', 'total_count','valid_count', 'invalid_count','match_percent', 'match')) expect_is(a[["standard"]],"character") expect_is(a[["match"]],"character") expect_is(a[["checks"]],"tbl") @@ -28,13 +28,16 @@ test_that("expected number of checks (in)valid",{ a<-evaluateStandard(data=adlbc_edit, standard="sdtm") expect_equal(a[["valid_count"]],2) expect_equal(a[["invalid_count"]],8) + expect_equal(a[["total_count"]],10) + expect_equal(a[["match_percent"]],.2) expect_true(a[["checks"]]%>%filter(text_key=="measure_col")%>%select(valid)%>%unlist) }) test_that("field level data is ignored when useFields=false",{ noFields<-evaluateStandard(data=adlbc, standard="adam", includeFields=FALSE) - expect_equal(noFields[["match"]],"Full") + expect_equal(noFields[["match"]],"full") + expect_equal(noFields[["match_percent"]],1) expect_equal(noFields[["valid_count"]],6) }) diff --git a/tests/testthat/test_getRequiredSettings.R b/tests/testthat/test_getRequiredSettings.R index b5d8ef11..b1d463cc 100644 --- a/tests/testthat/test_getRequiredSettings.R +++ b/tests/testthat/test_getRequiredSettings.R @@ -6,13 +6,13 @@ defaultRequiredSettings <- list( list("id_col"), list("value_col"), list("measure_col"), - list("normal_col_low"), - list("normal_col_high"), - list("studyday_col"), list("measure_values","ALT"), list("measure_values","AST"), list("measure_values","TB"), - list("measure_values","ALP") + list("measure_values","ALP"), + list("normal_col_low"), + list("normal_col_high"), + list("studyday_col") ) diff --git a/tests/testthat/test_getSettingsMetadata.R b/tests/testthat/test_getSettingsMetadata.R index 5da5c819..7d65b796 100644 --- a/tests/testthat/test_getSettingsMetadata.R +++ b/tests/testthat/test_getSettingsMetadata.R @@ -24,19 +24,19 @@ mergedMetadata = suppressWarnings(bind_rows( )) test_that("Default function copies the whole metadata dataframe",{ - default<-safetyGraphics:::getSettingsMetadata() + default<-safetyGraphics:::getSettingsMetadata(add_standards=FALSE) expect_is(default,"data.frame") expect_equal(dim(default), dim(rawMetadata)) }) test_that("Pulling from a custom metadata file works as expected",{ - custom<-safetyGraphics:::getSettingsMetadata(metadata=customMetadata) + custom<-safetyGraphics:::getSettingsMetadata(metadata=customMetadata, add_standards=FALSE) expect_is(custom,"data.frame") expect_equal(dim(custom), dim(customMetadata)) - merged<-safetyGraphics:::getSettingsMetadata(metadata=customMetadata) + merged<-safetyGraphics:::getSettingsMetadata(metadata=mergedMetadata, add_standards=FALSE) expect_is(custom,"data.frame") - expect_equal(dim(custom), dim(customMetadata)) + expect_equal(dim(merged), dim(mergedMetadata)) }) test_that("charts parameter works as expected",{ @@ -135,4 +135,13 @@ test_that("filter_expr parameters works as expected",{ expect_equal(safetyGraphics:::getSettingsMetadata(filter_expr=text_key=="id_col",cols="description"),"Unique subject identifier variable name.") expect_length(safetyGraphics:::getSettingsMetadata(filter_expr=column_type=="numeric",cols="text_key",chart="edish"),5) expect_length(safetyGraphics:::getSettingsMetadata(filter_expr=setting_required,cols="text_key",chart="edish"),10) - }) +}) + +test_that("add_standards parameters works as expected",{ + noStandards<-safetyGraphics:::getSettingsMetadata(metadata=customMetadata, add_standards=FALSE) + yesStandards<-safetyGraphics:::getSettingsMetadata(metadata=customMetadata) #included by default + expect_true(dim(noStandards)[2]< dim(yesStandards)[2]) + standardNames <- names(standardsMetadata) + expect_equal(intersect(standardNames, names(yesStandards)),standardNames) + expect_equal(intersect(standardNames, names(noStandards)),"text_key") +})