From 2fac542942fb8303c16fabba6d082699a24e3fa0 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Mon, 4 Mar 2019 10:40:37 -0800 Subject: [PATCH 01/98] update version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index b41d8a9b..be69d218 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: safetyGraphics Title: Create Interactive Graphics Related to Clinical Trial Safety -Version: 0.9.0 +Version: 0.10.0 Authors@R: c( person("Jeremy", "Wildfire", email = "jeremy_wildfire@rhoworld.com", role = c("cre","aut")), person("Becca", "Krouse", role="aut"), From 5c618c00250f223b3bbcd198add9e6df8c812158 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 4 Mar 2019 15:49:36 -0500 Subject: [PATCH 02/98] change module naming convention --- inst/eDISH_app/global.R | 4 ++-- .../{renderEDishChart.R => render_edish_chart.R} | 15 ++++++++++++--- ...enderEDishChartUI.R => render_edish_chartUI.R} | 10 ++++++---- 3 files changed, 20 insertions(+), 9 deletions(-) rename inst/eDISH_app/modules/renderChart/{renderEDishChart.R => render_edish_chart.R} (90%) rename inst/eDISH_app/modules/renderChart/{renderEDishChartUI.R => render_edish_chartUI.R} (69%) diff --git a/inst/eDISH_app/global.R b/inst/eDISH_app/global.R index a248f576..88557c36 100644 --- a/inst/eDISH_app/global.R +++ b/inst/eDISH_app/global.R @@ -15,8 +15,8 @@ library(haven) source('modules/renderSettings/renderSettingsUI.R') source('modules/renderSettings/renderSettings.R') -source('modules/renderChart/renderEDishChartUI.R') -source('modules/renderChart/renderEDishChart.R') +source('modules/renderChart/render_edish_chartUI.R') +source('modules/renderChart/render_edish_chart.R') source('modules/dataUpload/dataUploadUI.R') source('modules/dataUpload/dataUpload.R') diff --git a/inst/eDISH_app/modules/renderChart/renderEDishChart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R similarity index 90% rename from inst/eDISH_app/modules/renderChart/renderEDishChart.R rename to inst/eDISH_app/modules/renderChart/render_edish_chart.R index b900e9bf..aaeb3602 100644 --- a/inst/eDISH_app/modules/renderChart/renderEDishChart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -14,7 +14,7 @@ #' @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){ +render_edish_chart <- function(input, output, session, data, settings, valid){ ns <- session$ns @@ -22,14 +22,23 @@ renderEDishChart <- function(input, output, session, data, settings, valid){ output$chart <- renderEDISH({ req(data()) req(settings()) - + if (valid()==TRUE){ trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) eDISH(data = trimmed_data, settings = settings()) } else{ return() } - }) + }) + + + # output$chart_title = renderUI({ + # if (valid()==TRUE){ + # HTML(paste("eDISH", icon("check", class="ok"))) + # } else { + # HTML(paste("eDISH", icon("times", class="notok"))) + # } + # }) # insert export chart button if settings pass validation # remove button if validation fails diff --git a/inst/eDISH_app/modules/renderChart/renderEDishChartUI.R b/inst/eDISH_app/modules/renderChart/render_edish_chartUI.R similarity index 69% rename from inst/eDISH_app/modules/renderChart/renderEDishChartUI.R rename to inst/eDISH_app/modules/renderChart/render_edish_chartUI.R index 62a8fe14..dbad1f22 100644 --- a/inst/eDISH_app/modules/renderChart/renderEDishChartUI.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chartUI.R @@ -6,11 +6,13 @@ #' #' @return The UI for the Chart tab #' -renderEDishChartUI <- function(id){ +render_edish_chartUI <- function(id){ ns <- NS(id) - tagList( - eDISHOutput(ns("chart")) - ) + # #tagList( + # tabPanel("edish", + eDISHOutput(ns("chart")) #) + #) + } \ No newline at end of file From d1044bc09db7d5db3dedb37e9a8b11a59e60db13 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 4 Mar 2019 15:51:21 -0500 Subject: [PATCH 03/98] disable css to test --- inst/eDISH_app/www/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css index 1f292331..2620f204 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -1,5 +1,5 @@ /* --- hide the chartSelect div until we're ready to implement multiple charts --- */ -.chartSelect{ +/*.chartSelect{ display:none; } /* ------------------------------------------------------------------------------- */ From 6c52490c3a53986972a171b4ab3b6d0dd2c4c80f Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 4 Mar 2019 15:51:43 -0500 Subject: [PATCH 04/98] create chart tabs from server side --- inst/eDISH_app/server.R | 62 +++++++++++++++++++++++++++++++++-------- inst/eDISH_app/ui.R | 12 ++++---- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 76dfae74..7ee7612a 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -28,7 +28,6 @@ function(input, output, session){ # # reutrns updated settings and validation status settings_new <- callModule(renderSettings, "settingsUI", - # 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())) @@ -44,22 +43,61 @@ function(input, output, session){ }) # update charts navbar - output$chart_tab_title = renderUI({ - if (settings_new$status()$valid==TRUE){ - HTML(paste("Chart", icon("check", class="ok"))) + # output$chart_tab_title = renderUI({ + # if (settings_new$status()$valid==TRUE){ + # HTML(paste("Chart", icon("check", class="ok"))) + # } else { + # HTML(paste("Chart", icon("times", class="notok"))) + # } + # }) + + # ## this currently wipes away everything anytime there's a change in chart selections + observe({ + + charts <- settings_new$charts() + + # remove whole navMenu and all existing chart tabs + removeTab(inputId="tabs", target="Charts") + + # for each chart, append a new tab to the menu and place the module UI output + lapply(charts, function(chart){ + tabfun <- match.fun(paste0("render_", chart, "_chartUI")) + tabid <- paste0(chart, "_tab_title") + tabcode <- tabPanel(title = htmlOutput(tabid), tabfun(paste0("chart", chart))) + + appendTab(inputId = "tabs", + navbarMenu("Charts", tabcode)) + }) + }) + + allcharts <- c("edish") # grab from metadata - all available charts + + for (chart in allcharts){ + + name <- paste0(chart,"_tab_title") + + output[[name]] = renderUI({ + status <- settings_new$status()$valid + if(status==TRUE){ + label <- HTML(paste(chart, icon("check", class="ok"))) } else { - HTML(paste("Chart", icon("times", class="notok"))) + label <- HTML(paste(chart, icon("times", class="notok"))) } }) + + modfun <- match.fun(paste0("render_", chart, "_chart")) + callModule(modfun, paste0("chart", chart), + data = reactive(dataUpload_out$data_selected()), + settings = reactive(settings_new$settings()), + valid = reactive(settings_new$status()$valid)) + } - # module to render eDish chart - callModule(renderEDishChart, "chartEDish", - data = reactive(dataUpload_out$data_selected()), - settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()$valid)) - - + # # module to render eDish chart + # callModule(renderEDishChart, "chartEDish", + # data = reactive(dataUpload_out$data_selected()), + # settings = reactive(settings_new$settings()), + # valid = reactive(settings_new$status()$valid)) session$onSessionEnded(stopApp) diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index 27aa19ee..d87ef885 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -5,7 +5,7 @@ tagList( tags$head( tags$link(rel = "stylesheet", type = "text/css", href = "index.css") ), - navbarPage("eDISH Shiny app", + navbarPage("eDISH Shiny app", id="tabs", tabPanel(title = htmlOutput("data_tab_title"), dataUploadUI("datatab") ), @@ -13,10 +13,10 @@ tagList( fluidPage( renderSettingsUI("settingsUI") ) - ), - tabPanel(title = htmlOutput("chart_tab_title"), - id = "charttab", - renderEDishChartUI("chartEDish") - ) + ) + # tabPanel(title = htmlOutput("chart_tab_title"), + # id = "charttab", + # renderEDishChartUI("chartEDish") + # ) ) ) From 2bc83446867e3d4c87f849e2a09cb11230169689 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 8 Mar 2019 08:51:49 -0800 Subject: [PATCH 05/98] Update generateSettings.R --- R/generateSettings.R | 46 ++++++++++++-------------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index ac5530d1..fd63862e 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -5,7 +5,7 @@ #' The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, eDish is the only chart supported. #' #' @param standard The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"} -#' @param chart The chart for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}. +#' @param charts The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}. #' @param partial Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}. #' @param partial_keys Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}. #' @return A list containing the appropriate settings for the selected chart @@ -31,7 +31,7 @@ #' #' @export -generateSettings <- function(standard="None", chart="eDish", partial=FALSE, partial_keys=NULL){ +generateSettings <- function(standard="None", charts="eDish", partial=FALSE, partial_keys=NULL){ if(tolower(chart)!="edish"){ stop(paste0("Can't generate settings for the specified chart ('",chart,"'). Only the 'eDish' chart is supported for now.")) } @@ -61,37 +61,9 @@ generateSettings <- function(standard="None", chart="eDish", partial=FALSE, part } } - # build shell settings for each chart - # TODO: move these to `/data` eventually - shells<-list() - shells[["edish"]]<-list( - id_col = NULL, - value_col = NULL, - measure_col = NULL, - normal_col_low = NULL, - normal_col_high = NULL, - studyday_col=NULL, - visit_col = NULL, - visitn_col = NULL, - filters = NULL, - group_cols = NULL, - measure_values = list(ALT = NULL, - AST = NULL, - TB = NULL, - ALP = NULL), - baseline = list(value_col=NULL, - values=list()), - analysisFlag = list(value_col=NULL, - values=list()), - - x_options = c("ALT", "AST", "ALP"), - y_options = c("TB", "ALP"), - visit_window = 30, - r_ratio_filter = TRUE, - r_ratio_cut = 0, - showTitle = TRUE, - warningText = "Caution: This interactive graphic is not validated. Any clinical recommendations based on this tool should be confirmed using your organizations standard operating procedures." - ) + #generate the shell setting object for the chart + shell<-generateShell(chart=chart) + populateDefaults(shell) # loop through dataMappings and apply them to the shell if(standard %in% standardList){ @@ -100,5 +72,11 @@ generateSettings <- function(standard="None", chart="eDish", partial=FALSE, part } } + #replace defaults with custom values (if any) + shell[[chart]]<-applyCustomSettings(shell, customSettings) + for(setting in customSettigns){ + setSettingValue(shell,setting$key, setting$value) + } + return(shells[[chart]]) -} \ No newline at end of file +} From 4c21892cc74a3423ded99bcc3ca5cbe237602204 Mon Sep 17 00:00:00 2001 From: pburnsdata Date: Fri, 8 Mar 2019 16:57:23 -0500 Subject: [PATCH 06/98] started generateSettings refactor --- NAMESPACE | 1 + R/generateShell.R | 48 ++++++++++++++++++++++++++++++++++ data-raw/csv_to_rda.R | 20 ++++++++++++-- data-raw/defaults.rda | Bin 0 -> 54 bytes data-raw/generateDefaults.R | 29 ++++++++++++++++++++ data/fullSettingsMetadata.rda | Bin 0 -> 2025 bytes data/settingsMetadata.rda | Bin 1804 -> 1806 bytes man/generateSettings.Rd | 6 ++--- man/generateShell.Rd | 42 +++++++++++++++++++++++++++++ 9 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 R/generateShell.R create mode 100644 data-raw/defaults.rda create mode 100644 data-raw/generateDefaults.R create mode 100644 data/fullSettingsMetadata.rda create mode 100644 man/generateShell.Rd diff --git a/NAMESPACE b/NAMESPACE index 3acbd77b..b40b9299 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ export(detectStandard) export(eDISH) export(eDISHOutput) export(generateSettings) +export(generateShell) export(getRequiredSettings) export(getSettingsMetadata) export(renderEDISH) diff --git a/R/generateShell.R b/R/generateShell.R new file mode 100644 index 00000000..079a4cd1 --- /dev/null +++ b/R/generateShell.R @@ -0,0 +1,48 @@ +#' Generate a settings object based on a data standard +#' +#' This function returns a settings object for the eDish chart based on the specified data standard. +#' +#' The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, eDish is the only chart supported. +#' +#' @param standard The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"} +#' @param charts The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}. +#' @param partial Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}. +#' @param partial_keys Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}. +#' @return A list containing the appropriate settings for the selected chart +#' +#' @examples +#' +#' generateSettings(standard="SDTM") +#' generateSettings(standard="SdTm") #also ok +#' generateSettings(standard="ADaM") +#' pkeys<- c("id_col","measure_col","value_col") +#' generateSettings(standard="adam", partial=TRUE, partial_keys=pkeys) +#' +#' generateSettings(standard="a different standard") +#' #returns shell settings list with no data mapping +#' +#' \dontrun{ +#' generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. +#' } +#' +#' @importFrom dplyr "filter" +#' @importFrom stringr str_split +#' @importFrom rlang .data +#' +#' @export + +generateShell <- function(chart="eDish"){ + + chart="eDish" + + shell <- list() + + keys <- safetyGraphics::getSettingsMetadata( + charts = chart, + cols="text_key" + ) + + + + return(shell) +} diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index de6c8b5f..358051af 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -4,7 +4,23 @@ ablbc <- read.csv("data-raw/adlbc.csv") usethis::use_data(adlbc, overwrite = TRUE) settingsMetadata <- read.csv("data-raw/settingsMetadata.csv") -usethis::use_data(settingsMetadata, overwrite = TRUE) + +#merge defaults on to settingsMetadata +defaults <- readRDS("data-raw/defaults.rda") #why is this not working... grrrr + +# from https://gist.github.com/aammd/9ae2f5cce9afd799bafb +defaults_df <- list(default = defaults) +class(defaults_df) <- c("tbl_df", "data.frame") +attr(defaults_df, "row.names") <- .set_row_names(length(defaults)) +if (!is.null(names(defaults))) { + defaults_df$name <- names(defaults) +} + +fullSettingsMetadata <- merge(settingsMetadata, defaults_df, by.x=c("text_key"), + by.y=c("name")) + +usethis::use_data(fullSettingsMetadata, overwrite = TRUE) + standardsMetadata <- read.csv("data-raw/standardsMetadata.csv") -usethis::use_data(standardsMetadata, overwrite = TRUE) \ No newline at end of file +usethis::use_data(standardsMetadata, overwrite = TRUE) diff --git a/data-raw/defaults.rda b/data-raw/defaults.rda new file mode 100644 index 0000000000000000000000000000000000000000..38b9b81306f1db4a18c9083708f7192754b28b82 GIT binary patch literal 54 zcmb2|=3oE=w(bW>2?+^F32CV*2}x{5k}M4~CN{P<3VReUS+Y!Z+OsJl%nZj2KA%zp GDhB|!qY=LV literal 0 HcmV?d00001 diff --git a/data-raw/generateDefaults.R b/data-raw/generateDefaults.R new file mode 100644 index 00000000..26eecc91 --- /dev/null +++ b/data-raw/generateDefaults.R @@ -0,0 +1,29 @@ +defaults <- list( + id_col = NULL, + value_col = NULL, + measure_col = NULL, + "measure_values--ALT" = NULL, + "measure_values--AST" = NULL, + "measure_values--TB" = NULL, + "measure_values--ALP" = NULL, + normal_col_low = NULL, + normal_col_high = NULL, + studyday_col= NULL, + visit_col = NULL, + visitn_col = NULL, + filters = NULL, + group_cols = NULL, + "baseline--value_col" = NULL, + "baseline--values" = list(), + "analysisFlag--value_col"=NULL, + "analysisFlag--values" = list(), + x_options = c("ALT", "AST", "ALP"), + y_options = c("TB", "ALP"), + visit_window = 30, + r_ratio_filter = TRUE, + r_ratio_cut = 0, + showTitle = TRUE, + warningText = "Caution: This interactive graphic is not validated. Any clinical recommendations based on this tool should be confirmed using your organizations standard operating procedures." + ) + +saveRDS(defaults, file="data-raw/defaults.rda") diff --git a/data/fullSettingsMetadata.rda b/data/fullSettingsMetadata.rda new file mode 100644 index 0000000000000000000000000000000000000000..4c895ad8b4ba9ee942d04171ad4dea16d06b8141 GIT binary patch literal 2025 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 literal 0 HcmV?d00001 diff --git a/data/settingsMetadata.rda b/data/settingsMetadata.rda index 525b0f1d605c959217cd84734b67fd117ee4ce44..374d6ef88aff896df104f303767a0f0740203cb2 100644 GIT binary patch delta 1791 zcmV9*514Gdo0BA8DqtpgZP)eFWvcrci0(jUJ{?Pz@P9L8DCCrXT2$#>ATh#tF2n(N>k16KEnQ zIpHX8d8w^wF}z17c#E(JH@13hX=|)o7>u;ufQUn}Xer-A95~aaF#kCYF z#CCtDBk|Y#R~Gl4*TMb4M)b2?3CrM2>cR(@JSBN#*;eWh}k9 zSLnh$*d!#8gGe%9=U~v>KqQqut5UyYDXxgboOCn(+BW3nwf_*k@_N*}cPlqHwSU>B zakgH0z7bUNv>K*)z*sp_aHFi|L}Ymch%wKJ0JsDRu?>O~Ng!TkZ;c~;rPRPd;%Nm? z&Nd`M69hv?#pZxWTUtOi0fPY`{!Beg^t6(iOc0wwOJ^3aQ5c8_jci;Im;8+ZcYBdp~ zd6!|Yl*CjqMK#RiyjxB0N=Zv0h}EjPGZn2u=}4|;GLYL<(IQr2*{Te~Q-7zA$2@Yl z^}juo$`_D?%2p;f-?b-5C8RYDL^VpzVi`HwumO-*6v(|zx}eCLC=h2G_J+$qrc+GF zGQ)ptzlvN8CA6iWK-hwE1T(`yycQ9;c#H1U_$4!xlhZn81MtFkF7pNk4WNNE#4R9K z@40r6o>9ip2TVXubFw6^>VJi>1yG@8)t4g+0LaLj9WSH>H)9T2WvgO@z#h!adstII z^P-MXqXY`GB$^UZV?4DKl@+|I=YdOv#}?&8g5DLES!PN@5Nfxp^9D3{it9jqk~aT$mL~yndKF|1rYh&R z+{C3MFMgWOFm7g&=HQ#1NGNf1he|dgg_6l^3@I|QSR+TlLbIIr6Pa?POB^H}VO=DX zHXzLG3@|SHdvG0{Zeab!IF;-Crh7gJknurrVe%hYjJwp>iZ6|7ZiSX`~NKpq4T&F=@`8&ae{c*&P=G$ zV-o=3VPUN2zR(F`WTLz%q7F2h6B%-bC6Ne#GiZg;f;sc9Q%M=aA$K%{@C^!&sOroX zK&l!YfHX)G_}nGO=?^I6-tfVLLcfz{(qVp2MS!0}fuO;_*2qw;Ai*xZM5)_CC9*0? h(FXv_rRf9fV1L1-+|Ye#NOFt+7ji{7P>@nHblR(B53&FN delta 1789 zcmVCLRx4!F+o`-Q(3DYtW1#(C4Zj{ByE9&0000028a~I$r>7Fpgl%_ z^$h?6L5R=>L5TGl18RDMQ%y9J8WL?ys5Ah2jQ{`*0004?13&|S27u5s05sDe0MVvE z28@^>F#rH?0MHr+fCidm02(yN0MU~K1|R?q02%{8&;Zj+fCEOE02(r2fW!a*ksu)& z0e=7ro-~>@Jx>xn8iP+1XwxQvri_|s(WYfE|3r>vB-kb}O{Hdvw5-6JK@mC62}63? zn%a{a#Bz6ty8x4W=c?A0+QqSm%T?}H&dh9Or9tP-%ULak=n<;iE&a=L6;Uj zvCKGfWf$K-E6bFH^l@t9nO;asR!=DhuzxXWM=ZJtfr=4Pr)cS#AUR9{GE^`iniHiU zYD9{9otuD(LfnVb#IA5?ZLn-dN=c>kp2m)e&=Lb7G>IKd_~w+-T=UELPRdz)xmV}H zKCBWFNXeuPf|V;Fus{cj1;H6Z*vH|Z)`vE9U$ph!QO3UzD6Qq32VO%&%Gua`&3}#V zXDy8~%1No`SWgT`3jN=29DKxyY56w`_wz)am2#&wm`QJ+aNF z3r|@&=rEN|EqeZID+RZdx@_w*JZ#zJ>zEW`z@|m&Z`lS!*+7Fx-^v@U0+~%yAj=Ld z^8Rwjs#{801MUarpg}k^9KvB7xk$h6UxHILNj*cPU{Hbsy&pJWVA}{23_#Kadl#dG zPAH;igQg!Rx*1U`jY8l8sDDtkZq&Gfz%nu?2UP@sr!3-0B$aH>YzMP*p4SwRJm{mC zX+HX`UAv=p7BkCLNl{$Ou6QuhF~_^akXz!i>nzDgVhvKcX@QL%BD%~z*zOr3B!xdw zfP5)ZbwnT8o^wJ>KH{gaKGwmciY1E$dK~AHl1Ij9BogM(ZK&o{$$tyM|Cv*mt|rFc z$b2ZqhCmwm)+PjTl=lN9Yq`b}#PJ=Nnn;h_cq+#f`Ne`0rk)+@dkpn>3XCmKMce4M5ir8n2&u(frhXbQttrT>I z+h<+dU4dFk=!s1H1AjNE5DXPuP_rht6Uik%YUuP_zt}aOw_xq$J9q$%PKDbgEx7Z{H?e zckDno+qv--L4FEBw4t1Y!=R{l!=Fg#gtyzjpvbU!&2T{_?RXLA7cit;MG=@WOJeY9 z9N^(&sM2lT&VTa)1~i61K?j%AQ0GU)LDkN31}1ren8;mxw=*o4e2|QZo(-EzZCRrP zhFJhf0+|Uxfzm6Es)#V6Jfl3z;))d+q%;JODXw*)q)(Zs%&?gRlDndjwW}pU!&edE z<&d$&3o%wv2Zj)e4VwrPK2HTtm*1#jX4^^N63CeZKz~J!1te=@Py4W^V8n9tok<%8 zhBOJ41KwgJEG#aD+8_^Y1T$Z^q+u?zyhtE4Be~nIWD}1GG8bXQ&ExYNB!lq;5HfIZ zHk^GUIf>#V;s9u9V;W;aXp|5KZ$*j|I7c8zC(0Z2ile4522sB1^wdh}E1cI#L6B%V zzf)hEEPoY)GB-XxVZ<953yyHA6Db4)lqSKNS=2DAD_dc8qOulAG@y<)RNKeq^t^*1 zP&PGk=$I{se4-8n%+nE>Gee1>JccF&TW;3_a}J$clSIqRyzDhW-f@C>*wRd>(PI+; z|lSvrR-=v#H1+_|KjdQrwS4kWA%xEQ8FYi diff --git a/man/generateSettings.Rd b/man/generateSettings.Rd index f4bb5e9c..88fd37ee 100644 --- a/man/generateSettings.Rd +++ b/man/generateSettings.Rd @@ -4,13 +4,13 @@ \alias{generateSettings} \title{Generate a settings object based on a data standard} \usage{ -generateSettings(standard = "None", chart = "eDish", partial = FALSE, - partial_keys = NULL) +generateSettings(standard = "None", charts = "eDish", + partial = FALSE, partial_keys = NULL) } \arguments{ \item{standard}{The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"}} -\item{chart}{The chart for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}.} +\item{charts}{The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}.} \item{partial}{Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}.} diff --git a/man/generateShell.Rd b/man/generateShell.Rd new file mode 100644 index 00000000..77e0b066 --- /dev/null +++ b/man/generateShell.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/generateShell.R +\name{generateShell} +\alias{generateShell} +\title{Generate a settings object based on a data standard} +\usage{ +generateShell(chart = "eDish") +} +\arguments{ +\item{standard}{The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"}} + +\item{charts}{The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}.} + +\item{partial}{Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}.} + +\item{partial_keys}{Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}.} +} +\value{ +A list containing the appropriate settings for the selected chart +} +\description{ +This function returns a settings object for the eDish chart based on the specified data standard. +} +\details{ +The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, eDish is the only chart supported. +} +\examples{ + +generateSettings(standard="SDTM") +generateSettings(standard="SdTm") #also ok +generateSettings(standard="ADaM") +pkeys<- c("id_col","measure_col","value_col") +generateSettings(standard="adam", partial=TRUE, partial_keys=pkeys) + +generateSettings(standard="a different standard") +#returns shell settings list with no data mapping + +\dontrun{ +generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. +} + +} From 74795e44ed0eb99260c40b177f926bd0e8466e15 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 11 Mar 2019 11:29:29 -0400 Subject: [PATCH 07/98] update defaults to use tibble --- R/data_checks.R | 1 + data-raw/csv_to_rda.R | 13 ++------- data-raw/defaults.rda | Bin 54 -> 0 bytes data-raw/generateDefaults.R | 56 ++++++++++++++++++------------------ data/adlbc.rda | Bin 95773 -> 95761 bytes data/defaults.rda | Bin 0 -> 561 bytes 6 files changed, 31 insertions(+), 39 deletions(-) create mode 100644 R/data_checks.R delete mode 100644 data-raw/defaults.rda create mode 100644 data/defaults.rda diff --git a/R/data_checks.R b/R/data_checks.R new file mode 100644 index 00000000..0ae20a0b --- /dev/null +++ b/R/data_checks.R @@ -0,0 +1 @@ +#Statistical Checks \ No newline at end of file diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index 358051af..3c7c8a97 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -6,18 +6,9 @@ usethis::use_data(adlbc, overwrite = TRUE) settingsMetadata <- read.csv("data-raw/settingsMetadata.csv") #merge defaults on to settingsMetadata -defaults <- readRDS("data-raw/defaults.rda") #why is this not working... grrrr +defaults <- readRDS(file="data/defaults.rda") #why is this not working... grrrr -# from https://gist.github.com/aammd/9ae2f5cce9afd799bafb -defaults_df <- list(default = defaults) -class(defaults_df) <- c("tbl_df", "data.frame") -attr(defaults_df, "row.names") <- .set_row_names(length(defaults)) -if (!is.null(names(defaults))) { - defaults_df$name <- names(defaults) -} - -fullSettingsMetadata <- merge(settingsMetadata, defaults_df, by.x=c("text_key"), - by.y=c("name")) +fullSettingsMetadata <- merge(settingsMetadata, defaults_df, by="text_key") usethis::use_data(fullSettingsMetadata, overwrite = TRUE) diff --git a/data-raw/defaults.rda b/data-raw/defaults.rda deleted file mode 100644 index 38b9b81306f1db4a18c9083708f7192754b28b82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54 zcmb2|=3oE=w(bW>2?+^F32CV*2}x{5k}M4~CN{P<3VReUS+Y!Z+OsJl%nZj2KA%zp GDhB|!qY=LV diff --git a/data-raw/generateDefaults.R b/data-raw/generateDefaults.R index 26eecc91..018767b6 100644 --- a/data-raw/generateDefaults.R +++ b/data-raw/generateDefaults.R @@ -1,29 +1,29 @@ -defaults <- list( - id_col = NULL, - value_col = NULL, - measure_col = NULL, - "measure_values--ALT" = NULL, - "measure_values--AST" = NULL, - "measure_values--TB" = NULL, - "measure_values--ALP" = NULL, - normal_col_low = NULL, - normal_col_high = NULL, - studyday_col= NULL, - visit_col = NULL, - visitn_col = NULL, - filters = NULL, - group_cols = NULL, - "baseline--value_col" = NULL, - "baseline--values" = list(), - "analysisFlag--value_col"=NULL, - "analysisFlag--values" = list(), - x_options = c("ALT", "AST", "ALP"), - y_options = c("TB", "ALP"), - visit_window = 30, - r_ratio_filter = TRUE, - r_ratio_cut = 0, - showTitle = TRUE, - warningText = "Caution: This interactive graphic is not validated. Any clinical recommendations based on this tool should be confirmed using your organizations standard operating procedures." - ) +defaults <- tribble(~text_key, ~default, + "id_col", NULL, + "value_col", NULL, + "measure_col", NULL, + "measure_values--ALT", NULL, + "measure_values--AST", NULL, + "measure_values--TB", NULL, + "measure_values--ALP", NULL, + "normal_col_low", NULL, + "normal_col_high", NULL, + "studyday_col",NULL, + "visit_col", NULL, + "visitn_col", NULL, + "filters", NULL, + "group_cols", NULL, + "baseline--value_col", NULL, + "baseline--values", list(), + "analysisFlag--value_col", NULL, + "analysisFlag--values", list(), + "x_options", c("ALT", "AST", "ALP"), + "y_options", c("TB", "ALP"), + "visit_window", 30, + "r_ratio_filter", TRUE, + "r_ratio_cut", 0, + "showTitle", TRUE, + "warningText", "Caution: This interactive graphic is not validated. Any clinical recommendations based on this tool should be confirmed using your organizations standard operating procedures." + ) -saveRDS(defaults, file="data-raw/defaults.rda") +usethis::use_data(defaults, overwrite = TRUE) diff --git a/data/adlbc.rda b/data/adlbc.rda index aa28c2dba7fcd41b1cfea7d3ce9254d0688fdd1c..aea28611190dde1a171087a67c08848aa386ad90 100644 GIT binary patch literal 95761 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

>Lo2UwSE&@4+2DOGd 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 literal 95773 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 EQSBl_sQ>@~ diff --git a/data/defaults.rda b/data/defaults.rda new file mode 100644 index 0000000000000000000000000000000000000000..668dd3f7612d4466672a26d34d6217fe3aadb5a6 GIT binary patch literal 561 zcmV-10?z$HT4*^jL0KkKS&k+39smI~f6@Q{+@L@M5D|R@TtL6?-|#>H004l100F=O z{3UFRB~$dKMAXwwHj_<~Vl>DBlhnw00E~g6XqX|X=t=scMvR7l05Sj#2QZifzzKxH zX@CF($)`GD*x(xw zcG<@z5hFCrkK_v5B4S?6v5laM7_#@qW*|lEE?_=Srial&`QzI<-I6XFXZJ^B^!Sv% zd$N<2nZ&jLh9sA@iUFm2NeepxDe!cB=76b*v>JKyc4_pG(wubFDD8>!z7VX!%mgp0 zd1Rt%WQiq3Ol$*&I#V>^DO@#2=Vdbu^bKoL#gSChbT`v1bgwtLYiQnVh%z>7 zuAAiR+@_JyBT)Q!mQ&!Uty>1pD8AK370)bvbP8=|<^WBFQ-&7H0C>x3+Bh0T$O zO>~-N&Z!5qgU#hN=Xcx7cLiZHxfK9rq6Q#VKFk9NjMkzZFm5KS*>0T(>ra-oly3?d zA{f}-q5{@~3n7xHF;{j0Kq8m&L=C!`LcS?wauw05`euLz7x=r9DZ+$=aWAOwXD0}U literal 0 HcmV?d00001 From be556aae61ae0e83cf8373de860a8baa920fb6f4 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 11 Mar 2019 11:48:52 -0400 Subject: [PATCH 08/98] fix defaults.rda save and load --- data-raw/csv_to_rda.R | 4 ++-- data-raw/defaults.rda | Bin 0 -> 509 bytes data-raw/generateDefaults.R | 2 +- data/defaults.rda | Bin 561 -> 0 bytes data/fullSettingsMetadata.rda | Bin 2025 -> 1968 bytes data/standardsMetadata.rda | Bin 696 -> 699 bytes 6 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 data-raw/defaults.rda delete mode 100644 data/defaults.rda diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index 3c7c8a97..f43cf99c 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -6,9 +6,9 @@ usethis::use_data(adlbc, overwrite = TRUE) settingsMetadata <- read.csv("data-raw/settingsMetadata.csv") #merge defaults on to settingsMetadata -defaults <- readRDS(file="data/defaults.rda") #why is this not working... grrrr +defaults <- readRDS("data-raw/defaults.rda") #why is this not working... grrrr -fullSettingsMetadata <- merge(settingsMetadata, defaults_df, by="text_key") +fullSettingsMetadata <- merge(settingsMetadata, defaults, by="text_key") usethis::use_data(fullSettingsMetadata, overwrite = TRUE) diff --git a/data-raw/defaults.rda b/data-raw/defaults.rda new file mode 100644 index 0000000000000000000000000000000000000000..018b5ccd81d7422b32c977145f70c2c90809af02 GIT binary patch literal 509 zcmV7HYouPzsqDk!^}dYMN*h~)3Y+K3 zbR+s=f>yGuM)=D4miHX`d*_i(`U!A30D6KvDWvkkx)@Me(>8&R$Yu{tD5=GEJMYeV zM#YrZph5Lcx=#wqf74z)K*!YOZEhMbjXnYW47fU;sgs(YTC%da1q{lMhl%1vl(jxq zbCu~b>6M5v9at@OnTcB;4m((bkU#%1LJcze4wg&DEz>*0q<02MgY3t^WQ3BFM@Djm zWAXkSiBB;2VjQb+eu!HS^Rd*gkI;s8zNJ|u9hDmU1|j8C^(QV?gV$&9pbIrN)+^ z<&QDndB9p0OEe;4KH004l100F=O z{3UFRB~$dKMAXwwHj_<~Vl>DBlhnw00E~g6XqX|X=t=scMvR7l05Sj#2QZifzzKxH zX@CF($)`GD*x(xw zcG<@z5hFCrkK_v5B4S?6v5laM7_#@qW*|lEE?_=Srial&`QzI<-I6XFXZJ^B^!Sv% zd$N<2nZ&jLh9sA@iUFm2NeepxDe!cB=76b*v>JKyc4_pG(wubFDD8>!z7VX!%mgp0 zd1Rt%WQiq3Ol$*&I#V>^DO@#2=Vdbu^bKoL#gSChbT`v1bgwtLYiQnVh%z>7 zuAAiR+@_JyBT)Q!mQ&!Uty>1pD8AK370)bvbP8=|<^WBFQ-&7H0C>x3+Bh0T$O zO>~-N&Z!5qgU#hN=Xcx7cLiZHxfK9rq6Q#VKFk9NjMkzZFm5KS*>0T(>ra-oly3?d zA{f}-q5{@~3n7xHF;{j0Kq8m&L=C!`LcS?wauw05`euLz7x=r9DZ+$=aWAOwXD0}U diff --git a/data/fullSettingsMetadata.rda b/data/fullSettingsMetadata.rda index 4c895ad8b4ba9ee942d04171ad4dea16d06b8141..3c08eab1a3661a445e6c4ff760fd9a4fe9a7d088 100644 GIT binary patch delta 1957 zcmV;W2U_^)53mmsLRx4!F+o`-Q&}h7TxkFW@PCmKBY$5s6bMwpXaE2JC<8>Eik_#a zXaS?t10ZRj003xd=`{UO>S#SjsPzEao}iQ>c`52KC!&K*GyrG~00Te(13(QL00#p= zXf$XUGyoa^3{4F*4Kx7Jqd)^jhXX)pG-w$#02%-cO${^+Gyu_~Km$gH13+jrXc;sB z8UPGU4SzHZGyu_~Km$gCL_z`+CLy4VNrgQcGAF5#k?4Ta(t3kNhL2EaW~LsraG-8H za4BPZ7z8j_up0%7F&Gi3g0zT;9c9TZ8MP)h^Dmk-$b$Au5e&3~10&{uXlg+SKq*ok zkx|BRo=rI=wAILsM;&BZ^Uij2^GXce+@EsBfq$^*l0f0ia_*qMso5(MHWDsfH{?(~ zhAkL;apP*6x`>kC<;K0<8|-FnJFy^gS9GGVgdl$}7KB1yq_#qpL6JPTTuy+v(NBeL4Fw0U%hg7M36w+rQPh ztA85m1y-3z>*L#(`Xh6409= z3801~*pHE;!odk>V1cj<7=TBW{^pEXbAJFTlMs_&0kfA_y$tjRq1TXW@yB4dh0%`# zv-I_%Kj`C4^WlWE8!8<~mH^Hc$%_&?@GJ|8znI$G-{Yxd{ zRM@;>vAnqglrOP?WME2{O>NgNM_LH*uMeIyRIDZ_DK@uN!UY^-0T^LD`v!T`fPXC` z0|Mz~WhkMWbXZhJDuhOD2Q>5^RnV=KcbZOtr43 zVq?US<5yO7$5C)?D&mRdCp%11b8VLWct{A&Sww87S&{CuUzfW;D#eLhj4W-}24xvg zgE7BIaoqx~N2-JwVZUtt*~rf2Y=0|gIkGvm!LzS%fs?XpE!|d)A$-?iL`(IeDW_b( zeuV?GECGbV-3k{{s8O|0s|}fSgz}CzNL(1kDb<--#ma&rAygppioLX9U>O4bG8p$#93Qs6%C{9HhNje|)QOBM?1 zoadIZjD1FC%7tGICmqAzVui>ir{zdD1LG}KUiY9>D`AiW3sR;bw&K}dC^T_W^V!Uf z{j)rWW#*zI)Ol>X=f47?Jb#*(u}VsuXn$-T9>)bnRQZoxPE;>lZ~$oo_U$UKK*Qyn zfj$@3O$OvbL=7%R5YeiH-|P0fEnGa0*3n9FWkcMeiQXKVp1v&=bcWk!W(}^;wPy50 z_o^}YUiCr&g1OdFaLcao7*S9xL4v~eIR}KSPEzukm82U?R?&Cb#DAtxsrPD%W*gb4 zyxbIYCd8FoIQu(6qiQT*mTPEbm8xAUBx?KBElN{)N*ER*a>7yI3@*r`X4DzG!GvOA z2EpyXsC=BCn>NoVca01O_NCH{0#JBATqB)z2V=P#AtCzU@A;@h5ZIcb2;9bCwEi2{ zf!kM84#0b0UhC>45`Xv3!Um;=+-SL(kJe<4frR(C(6kKdrx$4<)7;ZRCSHR>3l;ba zCG|G`5{2u*aRB3P_n@*1_o>j}ydJ{yl$!xshymf8n! zPDKb&0TnkIt$#y_jX&2P+GcS`YhZ z1hFzuT7R6VXgF$Oh9;QsEM%`l3INg=BNTOMT?Y{wLm_(_LUB|a)96Rm!_wO!{`r7CjUH$HnnP%5P*f-Xiwe0SoG3^W?yfX|Og(h> literal 2025 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 diff --git a/data/standardsMetadata.rda b/data/standardsMetadata.rda index 4aaca682f6f4f6172187518a484082bcf6d37a7e..638a5e6690efcc5ef380dcfd8dda176c7ee7f97e 100644 GIT binary patch delta 682 zcmV;b0#*IE1-k_fLRx4!F+o`-Q&~k56aSG8AAg!u5R^?xKS`w1X`vpIMw$R<007a5 z9*_eks3iy^MAY*{r>Xi=$)s&f4^gIppaVc?&@^cE0!3127)DJRX@VGrh9)BfVgmv+ z8Uf5EfY2H=VHylZn3*v&7)F7R446ov?*On;g$EZRinJLe29+$%!k0v)SWMBa!dYV= zE`N4QirFU_xJk5}l$NqesvJoB;WD@kq!Xg4WzCwl6pcbvj?I>p>1vz;e|)6#+XO<0 zs9S)-&?2OeT=RnA(tI)iAj*LXq7_TMoi6N#cIYBJXr;+=^X@y|Wrl8ZqAy*qRp~E0 zW$b-F2g&~zH(xFanb8pnsjy9e*a%_}tA8+15XOdrYY;?eLTw~SXh5?w!H6{zNe!VA z1-{4;UD_*kqDNUIS5PYg{VMkXub+yVo&=T&J!s%n#Z{Xs{qnhk3#?Qf&$LswBcCbc z(-?~3>Ki2)f*4pT3lr%E@NiB%aPXIhF$0w{4=XEG)1kw~*T2cUk1RP0FEE~b>3`98 z=YN%xbjOTz3iMl<_@$n1g{+Fm!jK}W5kclL&GzRTSDb&wuq-yw+kwlce$hlxiDpwDq{CI@FZB)vcZam3RNI z3kU-f1E#3N%F`2c#e*kj7KHPMYSb~t{ab+Ww8Eq*8-Mi|7!6|Gdb3TCfqxAkL?y6_ zKShIdZDc#sp_RpyT;kD7j{_SDZ(S)Ml#JCNal!~k4 Q!wvo}pF delta 679 zcmV;Y0$BaK1-JzcLRx4!F+o`-Q&}`NRi=>+AAh0G|*_oFn|mr zCP9eA5~@8RkQx90000000MGyll|A4Vid3ND^ifu$dT?pUnb^tEB&;TQ*x@a;kQYNG z<$sKnjT|K!Zb=t1PP&{-`{P2$45Ma6QoD?-wN!;dMTWh0Nb*YqME=T?O|S@s0Z`a! ziH$`_n`awrmyf0(0u$8%RS>3I?!4!7(=zD!=Tw}RuHE;0OwiA?<%`#I)p?7yGxWY* z=gt22H&Zq$nh_BSsjy9e*a%`mO$=zQ0)GwWAd1k0+DMYnfnjB~Aks}FHiStQ>mZE> zklEUa3~3U$=@>3$Ti{yN$pW54RfxUlc&g;4#!kb8sA|@S%80*2Zynkbf^JD;i1axH z0h+*~i2|bbiM0s^Ni^V+f@h`%h-?q7WZ6aKWGA N?ntK!5(b9Kw7{t{Hjw}T From d105658fbd3da577f45e90248172795a5c9c1444 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 11 Mar 2019 12:15:39 -0400 Subject: [PATCH 09/98] start generateShell.R --- NAMESPACE | 1 - R/compare_cols.R | 38 +++++++++++++++++++++++++++ R/generateSettings.R | 2 +- R/generateShell.R | 48 +++++++++++----------------------- data-raw/csv_to_rda.R | 7 +++-- data/fullSettingsMetadata.rda | Bin 1968 -> 0 bytes data/settingsMetadata.rda | Bin 1806 -> 1964 bytes man/compare_cols.Rd | 29 ++++++++++++++++++++ man/generateShell.Rd | 32 ++++++----------------- 9 files changed, 94 insertions(+), 63 deletions(-) create mode 100644 R/compare_cols.R delete mode 100644 data/fullSettingsMetadata.rda create mode 100644 man/compare_cols.Rd diff --git a/NAMESPACE b/NAMESPACE index b40b9299..3acbd77b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,7 +4,6 @@ export(detectStandard) export(eDISH) export(eDISHOutput) export(generateSettings) -export(generateShell) export(getRequiredSettings) export(getSettingsMetadata) export(renderEDISH) diff --git a/R/compare_cols.R b/R/compare_cols.R new file mode 100644 index 00000000..32202fdb --- /dev/null +++ b/R/compare_cols.R @@ -0,0 +1,38 @@ +#' Compares contents of 2 vectors +#' +#' Function to compare contents of 2 vectors - used to summarize of which data columns are found in a given standard. Used in \code{detectStandard()} and \code{validateSettings()} +#' +#' @param data_cols A character vector with column names in the data frame +#' @param standard_cols A character vector with column names in the data standard +#' @return A list summarizing the comparison between \code{data_cols} and \code{standard_cols}. List has character vectors for "matched_columns", "extra_columns" and "missing_columns" parameters, and a boolean "match" parameter indicating that there are no missing columns. +#' +#' +#' @examples +#' #match == FALSE +#' safetyGraphics:::compare_cols(data_cols=c("a","b","c"), +#' standard_cols=c("d","e","f")) +#' +#' # match == TRUE +#' safetyGraphics:::compare_cols(names(adlbc), +#' safetyGraphics:::getRequiredColumns(standard="ADaM")) +#' @keywords internal + +compare_cols<-function(data_cols, standard_cols){ + compare_summary <- list() + compare_summary[["matched_columns"]]<-intersect(data_cols, standard_cols) + compare_summary[["extra_columns"]]<-setdiff(data_cols,standard_cols) + compare_summary[["missing_columns"]]<-setdiff(standard_cols,data_cols) + + #if there are no missing columns then call this a match + + if (length(compare_summary[["missing_columns"]])==0) { + compare_summary[["match"]] <- "Full" + } else if(length(compare_summary[["matched_columns"]])>0) { + compare_summary[["match"]] <- "Partial" + } else { + compare_summary[["match"]] <- "None" + } + + + return(compare_summary) +} diff --git a/R/generateSettings.R b/R/generateSettings.R index fd63862e..897d78e1 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -63,7 +63,7 @@ generateSettings <- function(standard="None", charts="eDish", partial=FALSE, par #generate the shell setting object for the chart shell<-generateShell(chart=chart) - populateDefaults(shell) + #populateDefaults(shell) what is this for... # loop through dataMappings and apply them to the shell if(standard %in% standardList){ diff --git a/R/generateShell.R b/R/generateShell.R index 079a4cd1..8d6e25e4 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -1,48 +1,30 @@ -#' Generate a settings object based on a data standard +#' Generate a default settings shell based on settings metadata #' -#' This function returns a settings object for the eDish chart based on the specified data standard. +#' This function returns a default settings objectbased on the chart(s) specified. #' -#' The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, eDish is the only chart supported. +#' The function is designed to work with valid safetyGraphics charts. #' -#' @param standard The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"} -#' @param charts The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}. -#' @param partial Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}. -#' @param partial_keys Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}. +#' @param charts The chart or chart(s) for which shells should be generated ("eDish" only for now) . Default: \code{"eDish"}. #' @return A list containing the appropriate settings for the selected chart #' #' @examples #' -#' generateSettings(standard="SDTM") -#' generateSettings(standard="SdTm") #also ok -#' generateSettings(standard="ADaM") -#' pkeys<- c("id_col","measure_col","value_col") -#' generateSettings(standard="adam", partial=TRUE, partial_keys=pkeys) -#' -#' generateSettings(standard="a different standard") -#' #returns shell settings list with no data mapping -#' -#' \dontrun{ -#' generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. -#' } -#' -#' @importFrom dplyr "filter" -#' @importFrom stringr str_split -#' @importFrom rlang .data -#' -#' @export - -generateShell <- function(chart="eDish"){ +#' generateShell(chart = "eDish") +#' +#' @keywords internal - chart="eDish" +generateShell <- function(charts="eDish"){ - shell <- list() - keys <- safetyGraphics::getSettingsMetadata( - charts = chart, - cols="text_key" - ) + #chart="eDish" # expand too multiple charts + defaultMappings <- safetyGraphics::getSettingsMetadata( + charts = charts, + cols=c("text_key","default") + ) + shell <- defaultMappings$default + names(shell) <- defaultMappings$text_key return(shell) } diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index f43cf99c..07dfc3cb 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -3,15 +3,14 @@ library(usethis) ablbc <- read.csv("data-raw/adlbc.csv") usethis::use_data(adlbc, overwrite = TRUE) -settingsMetadata <- read.csv("data-raw/settingsMetadata.csv") +partialSettingsMetadata <- read.csv("data-raw/settingsMetadata.csv") #merge defaults on to settingsMetadata defaults <- readRDS("data-raw/defaults.rda") #why is this not working... grrrr -fullSettingsMetadata <- merge(settingsMetadata, defaults, by="text_key") - -usethis::use_data(fullSettingsMetadata, overwrite = TRUE) +settingsMetadata <- merge(partialSettingsMetadata, defaults, by="text_key") +usethis::use_data(settingsMetadata, overwrite = TRUE) standardsMetadata <- read.csv("data-raw/standardsMetadata.csv") usethis::use_data(standardsMetadata, overwrite = TRUE) diff --git a/data/fullSettingsMetadata.rda b/data/fullSettingsMetadata.rda deleted file mode 100644 index 3c08eab1a3661a445e6c4ff760fd9a4fe9a7d088..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1968 zcmV;h2T%AyT4*^jL0KkKSts3GX#fTAfB*mg|My7ekQ4u>U*Nz0|M0*NfIt8MfCvBr z7ytl(00H0#Uo;d5RKjQg001ZhM4pPCr>JNFqtpW+X`lcAXldy*{ZZ;@Jx8eZ0NS3Q zlp=X4>M`j7fz( z8ZsxTk&)TSg;!fi!m4xsDiYJh#h6gEE%;X zHuEo%CA8JZjYl11TJz3!bMs0J-Q1sY z#(}Wtl0f0ia_*qMso5(MHWDsfH{?(~hAkL;apP*6x`>kC<;K0<8|-FnJFy^gS9GGV zgdl$}7KB1yq_#qpL6J zPTTuy+v(NBeL4Fw0U%hg7M36w+rQPhs~YMBR@}0?1;0G*yA!!bw#NDF`r#!e_y-hP zCkgs>&1>*&{FrBJZptRI_*Rv|x}@Pssd>{6byd2!DC`$O73Ksj;DNCNU;}6Yq=7A? zZ8jd`?>53_7k!}%nTS2cfn0_X(3>F%poS#akCCIo!3k(!fv^l1fJc@7=8Rc$04kFZ zlVAa}msq_F^ar8WkZbYBV7GdDMU{BLf2IWo0O#n{-%IM=FFyZ3i^;9#zn-m3Nv>fu#*$ zYiZ27)(g^-*}4^m-C1oEF{N0x>tbWXk>giZb;nU~Z7Sl4O4bG8p$#93nwPOkN}OnaY#tuR1x8f) zk6lhwFI{i|X#@7{Dz8Ap<(z>&7uHP%c}f@- zB67k};0!LvqGr??yTOEFVFtnNz^HtjpPM$%D0htv2ll1Xi~>-2KU^c7bq8a)8zCY3 z;P3gULlD@Sp$Oc@V6^@l*MZwtQx3p;U|#F$Bog<|!Um;=+-SL(kJe<4frR(C(6kKd zrx$4<)7;ZRCSHR>3l;baCG|G`5{2u*aRB3P_n@*1_o>jMF>#=6*n5KLy3()?!wx7$PvxdbEH(x5d%P&?z02%OafxW zqFoKNKprFz&3&_^fTwVd@WK(AJpH}>l_0EpWe8no8#fOFbfEGC6_b)nNxaxssAoAe zyh+Re(9p&-#)j@;Ss)KB|a)96Rm!_wO!{`r7CjUH$HnnP%5P*f-Xi@744C`c3Tt~7ww CCVSri diff --git a/data/settingsMetadata.rda b/data/settingsMetadata.rda index 374d6ef88aff896df104f303767a0f0740203cb2..175251fa24a2d962fad27711d15639c81dc4d119 100644 GIT binary patch literal 1964 zcmV;d2UGY$T4*^jL0KkKSsyQxUjPO0fB*mg|My7ekQ4u>U*Nz0|M0*NfIt8MfCvBr z7ytl(00H0#Uo;d1RKh3#0000QB=t2VgZvwBh+c227^OPntF{i9;4KHfNf7u zN)bGi^(pv`6aWAKdVpja0017K27mxK8UsP2K*^v0&;Vj+X`pGK28|j38Z13{xe z$)Etx0AgrqplP56jT!(NG&mXqL8Cy)pa9SSVrXfgX`lv;8UPwJ5+V=~nK2Cn$)i)% zHkulSO{f^u2A-24qeDliG&55VQaDgI9!M0iy^I1FELaVK#h8o;)InNAL=Lj#mJHgH z8+n(_8e~CxC5VPvK>@IQ&o<%M#4qQru>Qrti_`b#~wzhyQqmSM=mw%;l9RZ)2<{AO74)X zIOZN$n+aT8rXjpsxo|O8m0~brI>9hMn&LWQ7fTnWR9fH!9fD~u1r^_}f~rj=(bbpq zr)~YU?eyy6K8*c&0FW$L3ri3TZQkl!)r~3zR@{=i1;0G*yA!!bw#NB=Jy4R9d&7z? z6NLR5=C%9#ejGEli??SKw7wOkajYCBie7ZT+*NL_3OfccMR|b>cpz***Z|r9X&_5z z+f9d+_?uxf3%Q{SnTR~C0=Wz&p*BJjK@3T;ACaTIf)dcd17H|20FN{M%^0%g097WC zlW8Qja_bkw&xk%JUO}(R9uaQ~qaFu``u6nPw9_(hAM!KIc*Se;1{$udtt@_n6FX@< zXT~%9YPj!T$DVYQ<6#+XSwX&}BNoX2S z))uy$%dKF&DJ`3!SZ&po(L)+li*C3kJV_ojXq|D?TpMp(Q9Oj_X^Kv4@?WMB0yCCT z6_o5UJ;rPEbe$S+ThvQ zxWLKTe$~fmTC~O^tzAS+R#6n`+%PB5pm*W47E3MQp)M5)F_bLbzYBvPpM9bd3}uv? zYqrBMpm>N`2t1y-g7{O4Oxk406tx;)baDXOCfFv5NItmjGDJ!hKS_czbHvdP? zG-FdR4Vo4v1agkJ`ygZ`qe3a9DK0p0XS5{+E3j2rQWca52stRCCJ2cp5dO$J{4NTN zspvgbIZ(Y-zyYKW+O(>@0}qaJ1o&TBG#il#5Hz_MLq@6-f2+sgwQ%;|siKtP%7>PU zCwOvddibh1C z7#1RO!cpu77i3X0Y7E`r!Z9#|VD{iu8~;8{p6GU&upii$LN5tH-TNUN7f5zHk**RS zst+8Eh-^(s2;*ZfT0HkJ8@8T?9bow2URTgaCFVNF8kQV*(Q>mNsL39Ig!b{FXc^N^ zF9{*h)uw_>y#|IBEBA{f^tS#2h3mTF0mj|$L1Y*2Q=zSeiIA2+Tco%zEx&X16T ztDNABORPWcYzhcxZ5*cy;lnR6;1_w~D9hFdFRC$Jb%jAle zbY4lMGzN4)u2xdgDbCbsE)yV>t9)5dryM9srNdW<@^ea9n1OQ@rXZgrq>7AYf&|Z& zd+t7O!50Pwmf8n!PDKb&0TnkItwV{8KkmZXdk7KD)N`a%&JhDZnC`O!-M1adORA1A%>yn`WC z8rr>HiUQVl6+yhAr7;tvW^O0}t^1}%Bwt;Q2Iw6+xu}X4(7#1*8nE!JVxCu*msGfD zf#P5stS(JxKkc9r#K}Qwa-pE%sfrkyW5BVJy$~n^NMMXn)unVCL}?6#>uCw#jR9BU zJXwXv6@yDI0PEF6C-1yu4u6C^!uCG0`!t5o(x9kM{}*yaI8cxuFOy#nduPW0 literal 1806 zcmV+p2l4nqT4*^jL0KkKSyD4}+5iQ4|L_0*|M$q}kQ=|JU*Nz0|KPw7fIt8U00I~Q z0Du4i;0M1oNgFX(KmY&$0}T)=dSN3&O_T?y&>9*514Gdo0BA8DqtpgZP)eFIl|HLdzho({ zh{T+9Gyd8(ozP|h|aLK6f-N5$rVNLyM!HUWbHApT4}O!Typ znoJOzLQ7{Buu&L@2#s%pE;LaEKm%_OQI9Vj;+m&IW&^YxbQ@?}dP11NnNUvYNnC6+ z9l;IDWNSOyscDt~`tT;I_m^uj`4QhshmW#6q6G!a@I^9i;Im;8+ZcYBdp~d6!|Yl*CjqMK#RiyjxB0N=Zv0h}EjPGZn2u z=}4|;GLYL<(IQr2*{Te~Q>Tx|JaV}8zde-77m$R?Rwg&!wI@g=q%{siHA>E689Cap z0gzY}$h}Rvpvapj5N8|qhRZ;vQ%uM*!+&hQid+mOw56ax*n)BdGs8i=77@94i|*C< zB{P(h(>i7Y@WOX4^9BYDpn){REg)C#xpt7AQO3~+Oh8X_vLvqRg|G!sp=Q;WBMSh? z$eSH6qy;x)4q0WZVuZjR%*}gPQ$X{gj!~lo3bQ1d5>jJ4wG@>VysGDcON7T3h zVdJUPd0`Vri#qO7Nl#LLbRC`OC^Cnr_f+3P_h123f&4X+&M@@bB!|O#NX~8uNP(bi zG)T9*ztMlb#cVWVGr=_*LvhQ!r!^fRw%OjhYoIGhT@fjtcxKfi0fMWXkx`ZF@R&_Z zDnWw6>~aT$mL~yndKF|1rYh&R+{C3MFMgWOFm7g&=HQ#1NGNf1he|dgg_6l^3@I|Q zSR+TlLbIIr6Pa?POB^H}VO=DXHXzLG3@|S zB4~F8;JE z!zI#lhbB@IWl&_ohd;zsFXr3xlPAj+*dRfOR$iPiJiK&me#Z*1cq4vNdlP(L4nLGj;e?-qCBHK%h?nvGdN}mAX8k6 zGf1BsQJ}Dy1d_X=k*%vGLc>=P;o^|7#0xQ2Q3r+)iVd2G6Fz?0p5Hw~6GLq$fJ-7| z5djuA6p_5fpY~x+fr#SfInp%^3}_Q72gbxmSXf-Gv_KvN5Y6ufafG|aQ6PZKj-I{g zkWM@#$Xy2eHjm71k`KTTK*_@`8&ae{c*&P=G$V-o=3VPUN2zR(F`WTLz%q7F2h6B%-bC6Ne#GlkKDIrFYl zNg2Z-cQl0X4GNE_>dY2Esu~@DG)NQp+$G294=Ci`@WF#ZzmsOtVSZ0VfS*Hwpuxb_ w$WW~y!7jZ-soO#&vMNf^2LQ{Z=>zLvf5D~P(0yr0a*O{Laz!{$kWw>r+RLFB) standards for clinical trial data. Currently, eDish is the only chart supported. +The function is designed to work with valid safetyGraphics charts. } \examples{ -generateSettings(standard="SDTM") -generateSettings(standard="SdTm") #also ok -generateSettings(standard="ADaM") -pkeys<- c("id_col","measure_col","value_col") -generateSettings(standard="adam", partial=TRUE, partial_keys=pkeys) - -generateSettings(standard="a different standard") -#returns shell settings list with no data mapping - -\dontrun{ -generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. -} - +generateShell(chart = "eDish") + } +\keyword{internal} From 6ed2a376cc7d41f313f10e89ad203264ac3cdf13 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 11 Mar 2019 14:51:02 -0400 Subject: [PATCH 10/98] recording thoughts --- R/generateSettings.R | 23 +++++++++++++------ R/generateShell.R | 30 ++++++++++++++++++++++--- R/populateDefaults.R | 53 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 R/populateDefaults.R diff --git a/R/generateSettings.R b/R/generateSettings.R index 897d78e1..12d148ea 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -36,6 +36,9 @@ generateSettings <- function(standard="None", charts="eDish", partial=FALSE, par stop(paste0("Can't generate settings for the specified chart ('",chart,"'). Only the 'eDish' chart is supported for now.")) } + chart="eDish" + standard="sdtm" + # Check that partial_keys is supplied if partial is true if (is.null(partial_keys) & partial ) { stop("partial_keys must be supplied if the standard is partial") @@ -61,22 +64,28 @@ generateSettings <- function(standard="None", charts="eDish", partial=FALSE, par } } + shells<-list() #generate the shell setting object for the chart - shell<-generateShell(chart=chart) - #populateDefaults(shell) what is this for... + shells[[chart]]<-safetyGraphics:::generateShell(chart=chart) # will want to handle multiple charts eventually... + + + #populateDefaults() + # Currently the default assignment is happening in generateShell, but I think you would ideally make the shell (only key names) in + #generateShell and then merge defaults and standards together (overwritting defaults where there are standards) and then do the loop below # loop through dataMappings and apply them to the shell + if(standard %in% standardList){ for(row in 1:nrow(dataMappings)){ shells[[chart]]<-setSettingsValue(settings = shells[[chart]], key = textKeysToList(dataMappings[row,"text_key"])[[1]], value = dataMappings[row, "column_name"]) } } - #replace defaults with custom values (if any) - shell[[chart]]<-applyCustomSettings(shell, customSettings) - for(setting in customSettigns){ - setSettingValue(shell,setting$key, setting$value) - } + # #replace defaults with custom values (if any) + # shell[[chart]]<-applyCustomSettings(shell, customSettings) + # for(setting in customSettigns){ + # setSettingValue(shell,setting$key, setting$value) + # } return(shells[[chart]]) } diff --git a/R/generateShell.R b/R/generateShell.R index 8d6e25e4..7e4cc9a7 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -16,15 +16,39 @@ generateShell <- function(charts="eDish"){ - #chart="eDish" # expand too multiple charts + chart="eDish" # expand too multiple charts defaultMappings <- safetyGraphics::getSettingsMetadata( charts = charts, cols=c("text_key","default") ) + + hierarchical_metadata <- str_split(defaultMappings$text_key, "--") + + + - shell <- defaultMappings$default - names(shell) <- defaultMappings$text_key + shell <- list() + for (i in 1:length(hierarchical_metadata) ) { + + # Handle settings with one level + if (length(hierarchical_metadata[[i]]) == 1) { + + shell[defaultMappings$text_key[i]] = defaultMappings$default[i] + + # Handle settings with two levels + } else if (length(hierarchical_metadata[[i]]) == 2){ + + #Create list if it does not exist + if (!is.list(shell[[hierarchical_metadata[[i]][1]]])) { shell[[hierarchical_metadata[[i]][1]]] = list() } #Need to make list if it doesnt exist since its two-level + + shell[[hierarchical_metadata[[i]][1]]][hierarchical_metadata[[i]][2]] = defaultMappings$default[i] + + } else{ + stop("Three level setting nests are not currently supported") + } + + } return(shell) } diff --git a/R/populateDefaults.R b/R/populateDefaults.R new file mode 100644 index 00000000..b0e514ed --- /dev/null +++ b/R/populateDefaults.R @@ -0,0 +1,53 @@ +#' Generate a default settings shell based on settings metadata +#' +#' This function returns a default settings objectbased on the chart(s) specified. +#' +#' The function is designed to work with valid safetyGraphics charts. +#' +#' @param charts The chart or chart(s) for which shells should be generated ("eDish" only for now) . Default: \code{"eDish"}. +#' @return A list containing the appropriate settings for the selected chart +#' +#' @examples +#' +#' generateShell(chart = "eDish") +#' +#' @keywords internal + +populateDefaults <- function(charts="eDish", settings ){ + + + chart="eDish" # expand too multiple charts + + defaultMappings <- safetyGraphics::getSettingsMetadata( + charts = charts, + cols=c("text_key","default") + ) + + hierarchical_metadata <- str_split(defaultMappings$text_key, "--") + + + shell <- list() + #replace this !!!!!!!!!!! + for (i in 1:length(hierarchical_metadata) ) { + + # Handle settings with one level + if (length(hierarchical_metadata[[i]]) == 1) { + + shell[defaultMappings$text_key[i]] = defaultMappings$default[i] + + # Handle settings with two levels + } else if (length(hierarchical_metadata[[i]]) == 2){ + + #Create list if it does not exist + if (!is.list(shell[[hierarchical_metadata[[i]][1]]])) { shell[[hierarchical_metadata[[i]][1]]] = list() } #Need to make list if it doesnt exist since its two-level + + shell[[hierarchical_metadata[[i]][1]]][hierarchical_metadata[[i]][2]] = defaultMappings$default[i] + + } else{ + stop("Three level setting nests are not currently supported") + } + + } + + return(shell) +} From 61153e6909986470878bc7c611b902b42c2c8e6a Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 13 Mar 2019 09:41:38 -0700 Subject: [PATCH 11/98] refactor generate shell --- R/generateSettings.R | 22 +++++++--------------- R/generateShell.R | 36 +++++++++--------------------------- R/setSettingsValue.R | 15 ++++++++++----- 3 files changed, 26 insertions(+), 47 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index 12d148ea..f878b6e4 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -31,13 +31,7 @@ #' #' @export -generateSettings <- function(standard="None", charts="eDish", partial=FALSE, partial_keys=NULL){ - if(tolower(chart)!="edish"){ - stop(paste0("Can't generate settings for the specified chart ('",chart,"'). Only the 'eDish' chart is supported for now.")) - } - - chart="eDish" - standard="sdtm" +generateSettings <- function(standard="None", charts=NULL, partial=FALSE, partial_keys=NULL){ # Check that partial_keys is supplied if partial is true if (is.null(partial_keys) & partial ) { @@ -46,13 +40,16 @@ generateSettings <- function(standard="None", charts="eDish", partial=FALSE, par # Coerce options to lowercase standard<-tolower(standard) - chart<-tolower(chart) + charts<-tolower(charts) + + #create shell + shell<-safetyGraphics:::generateShell(charts=charts) # Build a table of data mappings for the selected standard and partial settings standardList<-c("adam","sdtm") #TODO: automatically generate this from metadata if(standard %in% standardList){ dataMappings <- safetyGraphics::getSettingsMetadata( - charts = chart, + charts = charts, cols=c("text_key",standard,"setting_required") ) %>% filter(.data$setting_required)%>% @@ -64,11 +61,6 @@ generateSettings <- function(standard="None", charts="eDish", partial=FALSE, par } } - shells<-list() - #generate the shell setting object for the chart - shells[[chart]]<-safetyGraphics:::generateShell(chart=chart) # will want to handle multiple charts eventually... - - #populateDefaults() # Currently the default assignment is happening in generateShell, but I think you would ideally make the shell (only key names) in #generateShell and then merge defaults and standards together (overwritting defaults where there are standards) and then do the loop below @@ -87,5 +79,5 @@ generateSettings <- function(standard="None", charts="eDish", partial=FALSE, par # setSettingValue(shell,setting$key, setting$value) # } - return(shells[[chart]]) + return(shell) } diff --git a/R/generateShell.R b/R/generateShell.R index 7e4cc9a7..99a28acd 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -13,42 +13,24 @@ #' #' @keywords internal -generateShell <- function(charts="eDish"){ - - - chart="eDish" # expand too multiple charts +generateShell <- function(charts=NULL){ defaultMappings <- safetyGraphics::getSettingsMetadata( charts = charts, - cols=c("text_key","default") + cols=c("text_key","default","col_mapping") ) hierarchical_metadata <- str_split(defaultMappings$text_key, "--") - - - shell <- list() for (i in 1:length(hierarchical_metadata) ) { - - # Handle settings with one level - if (length(hierarchical_metadata[[i]]) == 1) { - - shell[defaultMappings$text_key[i]] = defaultMappings$default[i] - - # Handle settings with two levels - } else if (length(hierarchical_metadata[[i]]) == 2){ - - #Create list if it does not exist - if (!is.list(shell[[hierarchical_metadata[[i]][1]]])) { shell[[hierarchical_metadata[[i]][1]]] = list() } #Need to make list if it doesnt exist since its two-level - - shell[[hierarchical_metadata[[i]][1]]][hierarchical_metadata[[i]][2]] = defaultMappings$default[i] - - } else{ - stop("Three level setting nests are not currently supported") - } - + shell<-safetyGraphics:::setSettingsValue( + key=hierarchical_metadata[[i]], + value=NA, #NA is prefered here since NULL deletes the element in the list + settings=shell, + forceCreate=TRUE + ) } - + return(shell) } diff --git a/R/setSettingsValue.R b/R/setSettingsValue.R index 8052d237..13cf9c16 100644 --- a/R/setSettingsValue.R +++ b/R/setSettingsValue.R @@ -21,17 +21,22 @@ #' @keywords internal -setSettingsValue <- function(key, value, settings){ - stopifnot( - typeof(settings)=="list" - ) +setSettingsValue <- function(key, value, settings, forceCreate=FALSE){ + + if(typeof(settings)!="list"){ + if(forceCreate){ + settings=list() + }else{ + stop("Settings is not a valid list object. Set forceCreate to TRUE and re-run if you want to create a new list and continue.") + } + } firstKey <- key[[1]] if(length(key)==1){ settings[[firstKey]]<-value return(settings) }else{ - settings[[firstKey]]<-setSettingsValue(settings = settings[[firstKey]],key = key[2:length(key)], value) + settings[[firstKey]]<-setSettingsValue(settings = settings[[firstKey]],key = key[2:length(key)], value=value, forceCreate=forceCreate) return(settings) } } \ No newline at end of file From aee1603aec5db0399a7a1d0169f8ccaa0eb53e84 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 13 Mar 2019 11:00:39 -0700 Subject: [PATCH 12/98] a bit more refactor --- R/generateSettings.R | 41 +++++++++++++++++++++++++++-------------- R/generateShell.R | 24 ++++++++++++------------ 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index f878b6e4..42a0a908 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -40,11 +40,18 @@ generateSettings <- function(standard="None", charts=NULL, partial=FALSE, partia # Coerce options to lowercase standard<-tolower(standard) - charts<-tolower(charts) + if(!is.null(charts)){ + charts<-tolower(charts) + } - #create shell + ############################################################################# + # create shell + ############################################################################# shell<-safetyGraphics:::generateShell(charts=charts) + ############################################################################# + # populate defaults settings using a data standard (data and field mappings) + ############################################################################# # Build a table of data mappings for the selected standard and partial settings standardList<-c("adam","sdtm") #TODO: automatically generate this from metadata if(standard %in% standardList){ @@ -61,23 +68,29 @@ generateSettings <- function(standard="None", charts=NULL, partial=FALSE, partia } } - #populateDefaults() - # Currently the default assignment is happening in generateShell, but I think you would ideally make the shell (only key names) in - #generateShell and then merge defaults and standards together (overwritting defaults where there are standards) and then do the loop below - # loop through dataMappings and apply them to the shell - if(standard %in% standardList){ for(row in 1:nrow(dataMappings)){ - shells[[chart]]<-setSettingsValue(settings = shells[[chart]], key = textKeysToList(dataMappings[row,"text_key"])[[1]], value = dataMappings[row, "column_name"]) + shell<-setSettingsValue(settings = shell, key = textKeysToList(dataMappings[row,"text_key"])[[1]], value = dataMappings[row, "column_name"]) } } - - # #replace defaults with custom values (if any) - # shell[[chart]]<-applyCustomSettings(shell, customSettings) - # for(setting in customSettigns){ - # setSettingValue(shell,setting$key, setting$value) - # } + + ############################################################################# + # populate defaults settings not using a data standard (non-mappings) + ############################################################################# + defaults <- safetyGraphics::getSettingsMetadata( + charts = charts, + filter = !.data$column_mapping & !.data$field_mapping, + cols=c("text_key","default") + ) + + if(partial){ + defaults<-defaults%>%filter(.data$text_key %in% partial_keys) + } + + for(row in 1:nrow(defaults)){ + shell<-setSettingsValue(settings = shell, key = textKeysToList(defaults[row,"text_key"])[[1]], value = defaults[row, "default"]) + } return(shell) } diff --git a/R/generateShell.R b/R/generateShell.R index 99a28acd..17537a1e 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -1,31 +1,31 @@ #' Generate a default settings shell based on settings metadata #' -#' This function returns a default settings objectbased on the chart(s) specified. +#' This function returns a default settings object based on the chart(s) specified. #' #' The function is designed to work with valid safetyGraphics charts. #' -#' @param charts The chart or chart(s) for which shells should be generated ("eDish" only for now) . Default: \code{"eDish"}. -#' @return A list containing the appropriate settings for the selected chart +#' @param charts The chart or chart(s) to include in the shell settings object +#' @return A list containing a setting shell (all values = NA) for the selected chart(s) #' #' @examples #' -#' generateShell(chart = "eDish") +#' generateShell(charts = "eDish") #' #' @keywords internal generateShell <- function(charts=NULL){ - - defaultMappings <- safetyGraphics::getSettingsMetadata( + print(charts) + keys <- safetyGraphics::getSettingsMetadata( charts = charts, - cols=c("text_key","default","col_mapping") - ) + cols=c("text_key") + ) %>% safetyGraphics:::textKeysToList() - hierarchical_metadata <- str_split(defaultMappings$text_key, "--") - + print(keys) shell <- list() - for (i in 1:length(hierarchical_metadata) ) { + + for (i in 1:length(keys) ) { shell<-safetyGraphics:::setSettingsValue( - key=hierarchical_metadata[[i]], + key=keys[[i]], value=NA, #NA is prefered here since NULL deletes the element in the list settings=shell, forceCreate=TRUE From c7878dc0a44bc5151a9c3f2fb4d8301d30bf7215 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 13 Mar 2019 11:04:25 -0700 Subject: [PATCH 13/98] remove stray print()s --- R/generateShell.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/R/generateShell.R b/R/generateShell.R index 17537a1e..c8be6081 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -14,13 +14,11 @@ #' @keywords internal generateShell <- function(charts=NULL){ - print(charts) keys <- safetyGraphics::getSettingsMetadata( charts = charts, cols=c("text_key") ) %>% safetyGraphics:::textKeysToList() - print(keys) shell <- list() for (i in 1:length(keys) ) { From 4f61b5088f7714ea7eb9c353dc74ac6faacb5e44 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Thu, 14 Mar 2019 07:24:57 -0700 Subject: [PATCH 14/98] more generateSettings refactor --- R/generateSettings.R | 96 ++++++++++++++++++++++++++++---------------- R/populateDefaults.R | 53 ------------------------ 2 files changed, 62 insertions(+), 87 deletions(-) delete mode 100644 R/populateDefaults.R diff --git a/R/generateSettings.R b/R/generateSettings.R index 42a0a908..321b7c1b 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -4,10 +4,12 @@ #' #' The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, eDish is the only chart supported. #' -#' @param standard The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"} -#' @param charts The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}. +#' @param standard The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"None"}. +#' @param charts The chart or chart(s) for which settings should be generated. Default: \code{NULL} (uses all available charts). +#' @param useDefaults Specifies whether default values from settingsMetadata should be included in the settings object. Default: \code{TRUE}. #' @param partial Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}. #' @param partial_keys Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}. +#' @param custom_settings a tibble with text_key and customValue columns specifiying customizations to be applied to the settings object. Default: \code{NULL}. #' @return A list containing the appropriate settings for the selected chart #' #' @examples @@ -25,13 +27,13 @@ #' generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. #' } #' -#' @importFrom dplyr "filter" +#' @importFrom dplyr "filter" full_join #' @importFrom stringr str_split #' @importFrom rlang .data #' #' @export -generateSettings <- function(standard="None", charts=NULL, partial=FALSE, partial_keys=NULL){ +generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, partial=FALSE, partial_keys=NULL, custom_settings=NULL){ # Check that partial_keys is supplied if partial is true if (is.null(partial_keys) & partial ) { @@ -44,52 +46,78 @@ generateSettings <- function(standard="None", charts=NULL, partial=FALSE, partia charts<-tolower(charts) } + charts=NULL + standard="adam" ############################################################################# - # create shell - ############################################################################# - shell<-safetyGraphics:::generateShell(charts=charts) - - ############################################################################# - # populate defaults settings using a data standard (data and field mappings) - ############################################################################# + # get keys & default values for settings using a data standard (data and field mappings) + ############################################################################ # Build a table of data mappings for the selected standard and partial settings - standardList<-c("adam","sdtm") #TODO: automatically generate this from metadata + standardList<-standardsMetadata%>%select(-text_key)%>%names + if(standard %in% standardList){ - dataMappings <- safetyGraphics::getSettingsMetadata( + dataDefaults <- safetyGraphics::getSettingsMetadata( charts = charts, cols=c("text_key",standard,"setting_required") ) %>% filter(.data$setting_required)%>% - rename("column_name" = standard)%>% - filter(.data$column_name != '') - - if(partial){ - dataMappings<-dataMappings%>%filter(.data$text_key %in% partial_keys) - } + select(-setting_required)%>% + rename("dataDefault" = standard)%>% + filter(.data$dataDefault != '') + }else{ + dataDefaults<-tibble("text_key","dataDefault", .rows=0) } - # loop through dataMappings and apply them to the shell - if(standard %in% standardList){ - for(row in 1:nrow(dataMappings)){ - shell<-setSettingsValue(settings = shell, key = textKeysToList(dataMappings[row,"text_key"])[[1]], value = dataMappings[row, "column_name"]) - } + if(partial){ + dataDefaults <-dataDefaults%>%filter(.data$text_key %in% partial_keys) } ############################################################################# - # populate defaults settings not using a data standard (non-mappings) + # get keys & default values for settings not using a data standard ############################################################################# - defaults <- safetyGraphics::getSettingsMetadata( - charts = charts, - filter = !.data$column_mapping & !.data$field_mapping, - cols=c("text_key","default") - ) + if(useDefaults){ + otherDefaults <- safetyGraphics::getSettingsMetadata( + charts = charts, + filter = !.data$column_mapping & !.data$field_mapping, + cols=c("text_key","default") + )%>% + rename("otherDefault"="default") + }else{ + otherDefaults = tibble("text_key","otherDefault", .rows=0) + } - if(partial){ - defaults<-defaults%>%filter(.data$text_key %in% partial_keys) + ############################################################################# + # merge all keys & default values + ############################################################################# + #print(dataDefaults) + #print(otherDefaults) + key_values <- full_join(dataDefaults, otherDefaults, by="text_key")%>% + mutate(default=ifelse(is.na(dataDefault),otherDefault,dataDefault)) + + ############################################################################# + # Apply custom settings (if any) + ############################################################################# + if(!is.null(custom_settings)){ + key_values<-full_join(key_values, custom_settings, by="text_key") + } else { + key_values$customValue<-NA } - for(row in 1:nrow(defaults)){ - shell<-setSettingsValue(settings = shell, key = textKeysToList(defaults[row,"text_key"])[[1]], value = defaults[row, "default"]) + key_values<-key_values %>% mutate(value=ifelse(is.na(.data$customValue), .data$default, .data$customValue)) + + ############################################################################# + # create shell settings object + ############################################################################# + shell<-safetyGraphics:::generateShell(charts=charts) + + ######################################################################################### + # populate the shell settings by looping through key_values and apply them to the shell + ######################################################################################### + for(row in 1:nrow(key_values)){ + shell<-safetyGraphics:::setSettingsValue( + settings = shell, + key = safetyGraphics:::textKeysToList(key_values[row,"text_key"])[[1]], + value = key_values[row, "value"][[1]] + ) } return(shell) diff --git a/R/populateDefaults.R b/R/populateDefaults.R deleted file mode 100644 index b0e514ed..00000000 --- a/R/populateDefaults.R +++ /dev/null @@ -1,53 +0,0 @@ -#' Generate a default settings shell based on settings metadata -#' -#' This function returns a default settings objectbased on the chart(s) specified. -#' -#' The function is designed to work with valid safetyGraphics charts. -#' -#' @param charts The chart or chart(s) for which shells should be generated ("eDish" only for now) . Default: \code{"eDish"}. -#' @return A list containing the appropriate settings for the selected chart -#' -#' @examples -#' -#' generateShell(chart = "eDish") -#' -#' @keywords internal - -populateDefaults <- function(charts="eDish", settings ){ - - - chart="eDish" # expand too multiple charts - - defaultMappings <- safetyGraphics::getSettingsMetadata( - charts = charts, - cols=c("text_key","default") - ) - - hierarchical_metadata <- str_split(defaultMappings$text_key, "--") - - - shell <- list() - #replace this !!!!!!!!!!! - for (i in 1:length(hierarchical_metadata) ) { - - # Handle settings with one level - if (length(hierarchical_metadata[[i]]) == 1) { - - shell[defaultMappings$text_key[i]] = defaultMappings$default[i] - - # Handle settings with two levels - } else if (length(hierarchical_metadata[[i]]) == 2){ - - #Create list if it does not exist - if (!is.list(shell[[hierarchical_metadata[[i]][1]]])) { shell[[hierarchical_metadata[[i]][1]]] = list() } #Need to make list if it doesnt exist since its two-level - - shell[[hierarchical_metadata[[i]][1]]][hierarchical_metadata[[i]][2]] = defaultMappings$default[i] - - } else{ - stop("Three level setting nests are not currently supported") - } - - } - - return(shell) -} From c6b00cad32a00b90067bb0156efac7f51fdcb98b Mon Sep 17 00:00:00 2001 From: jwildfire Date: Thu, 14 Mar 2019 09:44:17 -0700 Subject: [PATCH 15/98] remove compare cols --- R/compare_cols.R | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 R/compare_cols.R diff --git a/R/compare_cols.R b/R/compare_cols.R deleted file mode 100644 index 32202fdb..00000000 --- a/R/compare_cols.R +++ /dev/null @@ -1,38 +0,0 @@ -#' Compares contents of 2 vectors -#' -#' Function to compare contents of 2 vectors - used to summarize of which data columns are found in a given standard. Used in \code{detectStandard()} and \code{validateSettings()} -#' -#' @param data_cols A character vector with column names in the data frame -#' @param standard_cols A character vector with column names in the data standard -#' @return A list summarizing the comparison between \code{data_cols} and \code{standard_cols}. List has character vectors for "matched_columns", "extra_columns" and "missing_columns" parameters, and a boolean "match" parameter indicating that there are no missing columns. -#' -#' -#' @examples -#' #match == FALSE -#' safetyGraphics:::compare_cols(data_cols=c("a","b","c"), -#' standard_cols=c("d","e","f")) -#' -#' # match == TRUE -#' safetyGraphics:::compare_cols(names(adlbc), -#' safetyGraphics:::getRequiredColumns(standard="ADaM")) -#' @keywords internal - -compare_cols<-function(data_cols, standard_cols){ - compare_summary <- list() - compare_summary[["matched_columns"]]<-intersect(data_cols, standard_cols) - compare_summary[["extra_columns"]]<-setdiff(data_cols,standard_cols) - compare_summary[["missing_columns"]]<-setdiff(standard_cols,data_cols) - - #if there are no missing columns then call this a match - - if (length(compare_summary[["missing_columns"]])==0) { - compare_summary[["match"]] <- "Full" - } else if(length(compare_summary[["matched_columns"]])>0) { - compare_summary[["match"]] <- "Partial" - } else { - compare_summary[["match"]] <- "None" - } - - - return(compare_summary) -} From c9b7fee4457686d726345589b6dc7f684f60a607 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Thu, 14 Mar 2019 10:57:37 -0700 Subject: [PATCH 16/98] update docs. start fixing tests --- NAMESPACE | 1 + R/generateSettings.R | 17 ++++++++++----- R/setSettingsValue.R | 1 + man/compare_cols.Rd | 29 -------------------------- man/generateSettings.Rd | 13 ++++++++---- man/generateShell.Rd | 10 ++++----- man/setSettingsValue.Rd | 4 +++- tests/testthat/test_generateSettings.R | 16 +++++++------- 8 files changed, 38 insertions(+), 53 deletions(-) delete mode 100644 man/compare_cols.Rd diff --git a/NAMESPACE b/NAMESPACE index 3acbd77b..c39c9361 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ import(rmarkdown) import(shinyjs) importFrom(dplyr,"filter") importFrom(dplyr,filter) +importFrom(dplyr,full_join) importFrom(haven,read_sas) importFrom(magrittr,"%>%") importFrom(purrr,keep) diff --git a/R/generateSettings.R b/R/generateSettings.R index 321b7c1b..8156af48 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -46,8 +46,6 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par charts<-tolower(charts) } - charts=NULL - standard="adam" ############################################################################# # get keys & default values for settings using a data standard (data and field mappings) ############################################################################ @@ -82,7 +80,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par )%>% rename("otherDefault"="default") }else{ - otherDefaults = tibble("text_key","otherDefault", .rows=0) + otherDefaults <- tibble("text_key","otherDefault", .rows=0) } ############################################################################# @@ -90,8 +88,17 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par ############################################################################# #print(dataDefaults) #print(otherDefaults) - key_values <- full_join(dataDefaults, otherDefaults, by="text_key")%>% - mutate(default=ifelse(is.na(dataDefault),otherDefault,dataDefault)) + if(nrows(dataDefaults) == 0 & nrows(otherDefaults==0)){ + key_values <- tibble("text_key","dataDefault","otherDefault", .rows=0) + }else if(nrows(dataDefaults)==0){ + key_values<-otherDefaults %>% mutate(dataDefault=NA) + }else if(nrows(otherDefaults)==0){ + key_values<-dataDefaults %>% mutate(otherDefault=NA) + }else{ + key_values <- full_join(dataDefaults, otherDefaults, by="text_key") + } + + key_values <- key_values %>% mutate(default=ifelse(is.na(dataDefault),otherDefault,dataDefault)) ############################################################################# # Apply custom settings (if any) diff --git a/R/setSettingsValue.R b/R/setSettingsValue.R index 13cf9c16..cf8ce72d 100644 --- a/R/setSettingsValue.R +++ b/R/setSettingsValue.R @@ -5,6 +5,7 @@ #' @param key a list (like those provided by \code{getSettingKeys()}) defining the position of parameter in the settings object. #' @param value the value to set #' @param settings The settings list used to generate a chart like \code{eDISH()} +#' @param forceCreate Specifies whether the function should create a new list() when none exisits. This most commonly occurs when deeply nested objects. #' @return the updated settings object #' #' @examples diff --git a/man/compare_cols.Rd b/man/compare_cols.Rd deleted file mode 100644 index a5a67420..00000000 --- a/man/compare_cols.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/compare_cols.R -\name{compare_cols} -\alias{compare_cols} -\title{Compares contents of 2 vectors} -\usage{ -compare_cols(data_cols, standard_cols) -} -\arguments{ -\item{data_cols}{A character vector with column names in the data frame} - -\item{standard_cols}{A character vector with column names in the data standard} -} -\value{ -A list summarizing the comparison between \code{data_cols} and \code{standard_cols}. List has character vectors for "matched_columns", "extra_columns" and "missing_columns" parameters, and a boolean "match" parameter indicating that there are no missing columns. -} -\description{ -Function to compare contents of 2 vectors - used to summarize of which data columns are found in a given standard. Used in \code{detectStandard()} and \code{validateSettings()} -} -\examples{ -#match == FALSE -safetyGraphics:::compare_cols(data_cols=c("a","b","c"), - standard_cols=c("d","e","f")) - -# match == TRUE -safetyGraphics:::compare_cols(names(adlbc), - safetyGraphics:::getRequiredColumns(standard="ADaM")) -} -\keyword{internal} diff --git a/man/generateSettings.Rd b/man/generateSettings.Rd index 88fd37ee..2c14b94e 100644 --- a/man/generateSettings.Rd +++ b/man/generateSettings.Rd @@ -4,17 +4,22 @@ \alias{generateSettings} \title{Generate a settings object based on a data standard} \usage{ -generateSettings(standard = "None", charts = "eDish", - partial = FALSE, partial_keys = NULL) +generateSettings(standard = "None", charts = NULL, + useDefaults = TRUE, partial = FALSE, partial_keys = NULL, + custom_settings = NULL) } \arguments{ -\item{standard}{The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"SDTM"}} +\item{standard}{The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"None"}.} -\item{charts}{The chart or chart(s) for which standards should be generated ("eDish" only for now) . Default: \code{"eDish"}.} +\item{charts}{The chart or chart(s) for which settings should be generated. Default: \code{NULL} (uses all available charts).} + +\item{useDefaults}{Specifies whether default values from settingsMetadata should be included in the settings object. Default: \code{TRUE}.} \item{partial}{Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}.} \item{partial_keys}{Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}.} + +\item{custom_settings}{a tibble with text_key and customValue columns specifiying customizations to be applied to the settings object. Default: \code{NULL}.} } \value{ A list containing the appropriate settings for the selected chart diff --git a/man/generateShell.Rd b/man/generateShell.Rd index 4788880b..e68240a9 100644 --- a/man/generateShell.Rd +++ b/man/generateShell.Rd @@ -4,23 +4,23 @@ \alias{generateShell} \title{Generate a default settings shell based on settings metadata} \usage{ -generateShell(charts = "eDish") +generateShell(charts = NULL) } \arguments{ -\item{charts}{The chart or chart(s) for which shells should be generated ("eDish" only for now) . Default: \code{"eDish"}.} +\item{charts}{The chart or chart(s) to include in the shell settings object} } \value{ -A list containing the appropriate settings for the selected chart +A list containing a setting shell (all values = NA) for the selected chart(s) } \description{ -This function returns a default settings objectbased on the chart(s) specified. +This function returns a default settings object based on the chart(s) specified. } \details{ The function is designed to work with valid safetyGraphics charts. } \examples{ -generateShell(chart = "eDish") +generateShell(charts = "eDish") } \keyword{internal} diff --git a/man/setSettingsValue.Rd b/man/setSettingsValue.Rd index 250bc4c4..dcced802 100644 --- a/man/setSettingsValue.Rd +++ b/man/setSettingsValue.Rd @@ -4,7 +4,7 @@ \alias{setSettingsValue} \title{Set the value for a given named parameter} \usage{ -setSettingsValue(key, value, settings) +setSettingsValue(key, value, settings, forceCreate = FALSE) } \arguments{ \item{key}{a list (like those provided by \code{getSettingKeys()}) defining the position of parameter in the settings object.} @@ -12,6 +12,8 @@ setSettingsValue(key, value, settings) \item{value}{the value to set} \item{settings}{The settings list used to generate a chart like \code{eDISH()}} + +\item{forceCreate}{Specifies whether the function should create a new list() when none exisits. This most commonly occurs when deeply nested objects.} } \value{ the updated settings object diff --git a/tests/testthat/test_generateSettings.R b/tests/testthat/test_generateSettings.R index 78dbc96b..9081df7d 100644 --- a/tests/testthat/test_generateSettings.R +++ b/tests/testthat/test_generateSettings.R @@ -5,19 +5,17 @@ setting_names<-c("id_col","value_col","measure_col","normal_col_low","normal_col test_that("a list with the expected properties and structure is returned for all standards",{ expect_is(generateSettings(standard="None"),"list") - expect_named(generateSettings(standard="None"),setting_names) - expect_named(generateSettings(standard="None")[["measure_values"]], c("ALT","AST","TB","ALP")) + expect_equal(sort(names(generateSettings(standard="None"))),sort(setting_names)) + expect_equal(sort(names(generateSettings(standard="None")[["measure_values"]])), sort(c("ALT","AST","TB","ALP"))) expect_is(generateSettings(standard="ADaM"),"list") - expect_named(generateSettings(standard="ADaM"),setting_names) - expect_named(generateSettings(standard="ADaM")[["measure_values"]], c("ALT","AST","TB","ALP")) - + expect_equal(sort(names(generateSettings(standard="ADaM"))),sort(setting_names)) + expect_equal(sort(names(generateSettings(standard="ADaM")[["measure_values"]])), sort(c("ALT","AST","TB","ALP"))) expect_is(generateSettings(standard="SDTM"),"list") - expect_named(generateSettings(standard="SDTM"),setting_names) - expect_named(generateSettings(standard="SDTM")[["measure_values"]], c("ALT","AST","TB","ALP")) -}) + expect_equal(sort(names(generateSettings(standard="SDTM"))),sort(setting_names)) + expect_equal(sort(names(generateSettings(standard="SDTM")[["measure_values"]])), sort(c("ALT","AST","TB","ALP")))}) -test_that("a warning is thrown if chart isn't eDish",{ +test_that("a warning is thrown if chart isn't found in the chart list",{ expect_error(generateSettings(chart="aeexplorer")) expect_error(generateSettings(chart="")) expect_silent(generateSettings(chart="eDish")) From 2660ce1b2db9c48914e0d5757c3f417665dab2e1 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 15 Mar 2019 10:44:47 -0700 Subject: [PATCH 17/98] fix bug with 0-row merge --- R/generateSettings.R | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index 8156af48..de7aebdd 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -62,7 +62,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par rename("dataDefault" = standard)%>% filter(.data$dataDefault != '') }else{ - dataDefaults<-tibble("text_key","dataDefault", .rows=0) + dataDefaults<-tibble(text_key=character(),dataDefault=character(), .rows=0) } if(partial){ @@ -80,7 +80,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par )%>% rename("otherDefault"="default") }else{ - otherDefaults <- tibble("text_key","otherDefault", .rows=0) + otherDefaults <- tibble(text_key=character(),otherDefault=character(), .rows=0) } ############################################################################# @@ -88,16 +88,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par ############################################################################# #print(dataDefaults) #print(otherDefaults) - if(nrows(dataDefaults) == 0 & nrows(otherDefaults==0)){ - key_values <- tibble("text_key","dataDefault","otherDefault", .rows=0) - }else if(nrows(dataDefaults)==0){ - key_values<-otherDefaults %>% mutate(dataDefault=NA) - }else if(nrows(otherDefaults)==0){ - key_values<-dataDefaults %>% mutate(otherDefault=NA) - }else{ - key_values <- full_join(dataDefaults, otherDefaults, by="text_key") - } - + key_values <- full_join(dataDefaults, otherDefaults, by="text_key") key_values <- key_values %>% mutate(default=ifelse(is.na(dataDefault),otherDefault,dataDefault)) ############################################################################# From 88c7399db9f812820ccd281a8a4a2377f15873ca Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 15 Mar 2019 12:23:05 -0700 Subject: [PATCH 18/98] update generateSettings tests --- tests/testthat/test_generateSettings.R | 30 +++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test_generateSettings.R b/tests/testthat/test_generateSettings.R index 9081df7d..4bd49f3a 100644 --- a/tests/testthat/test_generateSettings.R +++ b/tests/testthat/test_generateSettings.R @@ -28,13 +28,13 @@ test_that("data mappings are null when setting=none, character otherwise",{ none_settings <- generateSettings(standard="None") for(text_key in data_setting_keys){ key<-textKeysToList(text_key)[[1]] - expect_null(getSettingValue(settings=none_settings,key=key)) + expect_equal(getSettingValue(settings=none_settings,key=key),NA) } other_settings <- generateSettings(standard="a different standard") for(text_key in data_setting_keys){ key<-textKeysToList(text_key)[[1]] - expect_null(getSettingValue(settings=other_settings,key=key)) + expect_equal(getSettingValue(settings=other_settings,key=key),NA) } sdtm_settings <- generateSettings(standard="SDTM") @@ -71,7 +71,7 @@ test_that("data mappings are null when setting=none, character otherwise",{ if (text_key %in% c("id_col","measure_col","measure_values--ALT")) { expect_is(getSettingValue(settings=partial_adam_settings,key=key),"character") } else { - expect_null(getSettingValue(settings=partial_adam_settings,key=key)) + expect_equal(getSettingValue(settings=partial_adam_settings,key=key),NA) } } @@ -84,4 +84,28 @@ test_that("data mappings are null when setting=none, character otherwise",{ #Testing failure when partial is true with no specified columns expect_error(partial_settings_no_cols <- generateSettings(standard="ADaM", partial=TRUE)) + + #Test useDefaults + noDefaults <- generateSettings(standard="adam",useDefaults=FALSE) + option_keys<-c("x_options", "y_options", "visit_window", "r_ratio_filter", "r_ratio_cut", "showTitle", "warningText") + + #non data mappings are NA + for(text_key in option_keys){ + key<-textKeysToList(text_key)[[1]] + expect_equal(getSettingValue(settings=noDefaults,key=key),NA) + } + + #data mappings are filled as expected + for(text_key in data_setting_keys){ + key<-textKeysToList(text_key)[[1]] + expect_is(getSettingValue(settings=noDefaults,key=key),"character") + } + + #Test customSettings + customizations<- tibble(text_key=c("id_col","warningText","measure_values--ALT"),customValue=c("customID","This is a custom warning","custom ALT")) + customSettings<-generateSettings(standard="adam",custom_settings=customizations) + expect_equal(getSettingValue(settings=customSettings,key=list("id_col")),"customID") + expect_equal(getSettingValue(settings=customSettings,key=list("warningText")),"This is a custom warning") + expect_equal(getSettingValue(settings=customSettings,key=list("measure_values","ALT")),"custom ALT") + expect_equal(getSettingValue(settings=customSettings,key=list("measure_col")),"PARAM") }) From bac9b1874c69333c88e9387a8fe1ab854977b3e6 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 15 Mar 2019 12:57:30 -0700 Subject: [PATCH 19/98] update checks to support NA and NULL settings --- R/checkColumn.R | 2 +- R/checkNumeric.R | 2 +- tests/testthat/test_getRequiredSettings.R | 8 ++++---- tests/testthat/test_getSettingValue.R | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/R/checkColumn.R b/R/checkColumn.R index 3674be7b..ff80d9fc 100644 --- a/R/checkColumn.R +++ b/R/checkColumn.R @@ -42,7 +42,7 @@ checkColumn <- function(key, settings, data){ current$type <- "column" current$description <- "column parameter from setting setting found in data?" current$value <- getSettingValue(key=key,settings=settings) - if(is.null(current$value)){ + if(is.na(current$value)||is.null(current$value)){ current$value <- "--No Value Given--" current$valid <- TRUE current$message <- "" diff --git a/R/checkNumeric.R b/R/checkNumeric.R index 435d4627..1dbe2983 100644 --- a/R/checkNumeric.R +++ b/R/checkNumeric.R @@ -25,7 +25,7 @@ checkNumeric <- function(key, settings, data){ current$type <- "numeric" current$description <- "specified column is numeric?" current$value <- getSettingValue(key=key,settings=settings) - if(is.null(current$value)){ + if(is.na(current$value)||is.null(current$value)){ current$value <- "--No Value Given--" current$valid <- TRUE current$message <- "" diff --git a/tests/testthat/test_getRequiredSettings.R b/tests/testthat/test_getRequiredSettings.R index b1d463cc..ea3fed4a 100644 --- a/tests/testthat/test_getRequiredSettings.R +++ b/tests/testthat/test_getRequiredSettings.R @@ -4,15 +4,15 @@ library(testthat) defaultRequiredSettings <- list( list("id_col"), - list("value_col"), list("measure_col"), + list("measure_values","ALP"), list("measure_values","ALT"), list("measure_values","AST"), list("measure_values","TB"), - list("measure_values","ALP"), - list("normal_col_low"), list("normal_col_high"), - list("studyday_col") + list("normal_col_low"), + list("studyday_col"), + list("value_col") ) diff --git a/tests/testthat/test_getSettingValue.R b/tests/testthat/test_getSettingValue.R index 59f5ac00..ea32427a 100644 --- a/tests/testthat/test_getSettingValue.R +++ b/tests/testthat/test_getSettingValue.R @@ -14,7 +14,7 @@ test_that("different data types for `key` parameter work as expected",{ expect_equal(getSettingValue(key="id_col",settings=testSettings),"USUBJID") expect_equal(getSettingValue(key=c("measure_values","ALT"),settings=testSettings),"Aminotransferase, alanine (ALT)") expect_equal(getSettingValue(key=list("measure_values","ALT"),settings=testSettings),"Aminotransferase, alanine (ALT)") - expect_equal(getSettingValue(key=list("measure_values",1),settings=testSettings),"Aminotransferase, alanine (ALT)") + expect_equal(getSettingValue(key=list("measure_values",2),settings=testSettings),"Aminotransferase, alanine (ALT)") }) test_that("returns null if the setting isn't found",{ From ddbbbb7767a1d833251f4ceba5bb7bf7be480036 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 15 Mar 2019 13:06:16 -0700 Subject: [PATCH 20/98] update trim_data --- R/trimData.R | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/R/trimData.R b/R/trimData.R index f50fe288..fb150005 100644 --- a/R/trimData.R +++ b/R/trimData.R @@ -50,22 +50,25 @@ trimData <- function(data, settings, chart="edish"){ data_subset <- select(data, unlist(common_cols)) ## Remove rows if baseline or analysisFlag is specified ## - - if(!is.null(settings[['baseline']][['value_col']]) | !is.null(settings[['analysisFlag']][['value_col']])) { + baselineSetting<-settings[['baseline']][['value_col']] + baselineMissing <- is.null(baselineSetting) || is.na(baselineSetting) + analysisSetting<-settings[['analysisFlag']][['value_col']] + analysisMissing <- is.null(analysisSetting) || is.na(analysisSetting) + + if(!baselineMissing | !analysisMissing) { # Create Baseline String - baseline_string <- ifelse(!is.null(settings[['baseline']][['value_col']]), + baseline_string <- ifelse(!baselineMissing, paste(settings[['baseline']][['value_col']], "%in% settings[['baseline']][['values']]"), "") # Create AnalysisFlag String - analysis_string <- ifelse(!is.null(settings[['analysisFlag']][['value_col']]), + analysis_string <- ifelse(!analysisMissing, paste(settings[['analysisFlag']][['value_col']], "%in% settings[['analysisFlag']][['values']]"), "") # Include OR operator if both are specified - operator <- ifelse(!is.null(settings[['baseline']][['value_col']]) & !is.null(settings[['analysisFlag']][['value_col']]), - "|","") + operator <- ifelse(!baselineMissing & !analysisMissing, "|", "") # Create filter string and make it an expression filter_string <- paste(baseline_string, operator, analysis_string) From a7e4f098ccde20009eec1ebdf49ec377c962e956 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Mon, 18 Mar 2019 08:14:20 -0700 Subject: [PATCH 21/98] add simple generateShell tests --- tests/testthat/test_generateShell.R | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/testthat/test_generateShell.R diff --git a/tests/testthat/test_generateShell.R b/tests/testthat/test_generateShell.R new file mode 100644 index 00000000..c271a10b --- /dev/null +++ b/tests/testthat/test_generateShell.R @@ -0,0 +1,13 @@ +context("Tests for the generateShell() function") +library(safetyGraphics) + +default <- generateShell() + +test_that("a list with the expected properties and structure is returned by default",{ + expect_type(default, "list") + expect_equal(default[["id_col"]],NA) + expect_equal(default[["measure_values"]][["ALT"]],NA) + expect_null(default[["not_a_setting"]]) +}) + +# TODO: Add tests for the charts parameter once multiple charts are added \ No newline at end of file From a9873778a0564e1098d47fba7a84cea6f1781f3a Mon Sep 17 00:00:00 2001 From: jwildfire Date: Mon, 18 Mar 2019 08:39:27 -0700 Subject: [PATCH 22/98] clear checks --- R/generateSettings.R | 14 ++++++-------- R/generateShell.R | 24 ++++++++++++------------ R/settingsMetadata.R | 1 + man/generateShell.Rd | 4 ++-- man/settingsMetadata.Rd | 1 + 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index de7aebdd..7fd2e4cd 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -50,7 +50,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par # get keys & default values for settings using a data standard (data and field mappings) ############################################################################ # Build a table of data mappings for the selected standard and partial settings - standardList<-standardsMetadata%>%select(-text_key)%>%names + standardList<-safetyGraphics::standardsMetadata%>%select(-.data$text_key)%>%names if(standard %in% standardList){ dataDefaults <- safetyGraphics::getSettingsMetadata( @@ -58,7 +58,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par cols=c("text_key",standard,"setting_required") ) %>% filter(.data$setting_required)%>% - select(-setting_required)%>% + select(-.data$setting_required)%>% rename("dataDefault" = standard)%>% filter(.data$dataDefault != '') }else{ @@ -86,10 +86,8 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par ############################################################################# # merge all keys & default values ############################################################################# - #print(dataDefaults) - #print(otherDefaults) key_values <- full_join(dataDefaults, otherDefaults, by="text_key") - key_values <- key_values %>% mutate(default=ifelse(is.na(dataDefault),otherDefault,dataDefault)) + key_values <- key_values %>% mutate(default=ifelse(is.na(.data$dataDefault),.data$otherDefault,.data$dataDefault)) ############################################################################# # Apply custom settings (if any) @@ -105,15 +103,15 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par ############################################################################# # create shell settings object ############################################################################# - shell<-safetyGraphics:::generateShell(charts=charts) + shell<-generateShell(charts=charts) ######################################################################################### # populate the shell settings by looping through key_values and apply them to the shell ######################################################################################### for(row in 1:nrow(key_values)){ - shell<-safetyGraphics:::setSettingsValue( + shell<-setSettingsValue( settings = shell, - key = safetyGraphics:::textKeysToList(key_values[row,"text_key"])[[1]], + key = textKeysToList(key_values[row,"text_key"])[[1]], value = key_values[row, "value"][[1]] ) } diff --git a/R/generateShell.R b/R/generateShell.R index c8be6081..e6ba68ba 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -1,31 +1,31 @@ #' Generate a default settings shell based on settings metadata #' -#' This function returns a default settings object based on the chart(s) specified. +#' This function returns a default settings object based on the chart(s) specified. #' #' The function is designed to work with valid safetyGraphics charts. #' #' @param charts The chart or chart(s) to include in the shell settings object #' @return A list containing a setting shell (all values = NA) for the selected chart(s) -#' -#' @examples -#' -#' generateShell(charts = "eDish") -#' +#' +#' @examples +#' +#' safetyGraphics:::generateShell(charts = "eDish") +#' #' @keywords internal -generateShell <- function(charts=NULL){ +generateShell <- function(charts=NULL){ keys <- safetyGraphics::getSettingsMetadata( - charts = charts, + charts = charts, cols=c("text_key") - ) %>% safetyGraphics:::textKeysToList() + ) %>% textKeysToList() shell <- list() for (i in 1:length(keys) ) { - shell<-safetyGraphics:::setSettingsValue( - key=keys[[i]], + shell<-setSettingsValue( + key=keys[[i]], value=NA, #NA is prefered here since NULL deletes the element in the list - settings=shell, + settings=shell, forceCreate=TRUE ) } diff --git a/R/settingsMetadata.R b/R/settingsMetadata.R index 92882d95..e2ba463e 100644 --- a/R/settingsMetadata.R +++ b/R/settingsMetadata.R @@ -15,6 +15,7 @@ #' \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{setting_cat}{Setting category (data, measure, appearance)} +#' \item{default}{Default value for non-data settings} #' } #' #' @source Created for this package diff --git a/man/generateShell.Rd b/man/generateShell.Rd index e68240a9..d66a4d57 100644 --- a/man/generateShell.Rd +++ b/man/generateShell.Rd @@ -20,7 +20,7 @@ The function is designed to work with valid safetyGraphics charts. } \examples{ -generateShell(charts = "eDish") - +safetyGraphics:::generateShell(charts = "eDish") + } \keyword{internal} diff --git a/man/settingsMetadata.Rd b/man/settingsMetadata.Rd index 7574fe69..dcadfa89 100644 --- a/man/settingsMetadata.Rd +++ b/man/settingsMetadata.Rd @@ -17,6 +17,7 @@ \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{setting_cat}{Setting category (data, measure, appearance)} + \item{default}{Default value for non-data settings} }} \source{ Created for this package From 00af7234abdaa9e1d6d89ec19b4a5c9aff9c0bdc Mon Sep 17 00:00:00 2001 From: jwildfire Date: Mon, 18 Mar 2019 12:20:55 -0700 Subject: [PATCH 23/98] use NULL instead of NA in shell --- R/checkColumn.R | 20 +++---- R/checkNumeric.R | 10 ++-- R/generateSettings.R | 74 +++++++++++++------------- R/generateShell.R | 2 +- R/setSettingsValue.R | 6 ++- R/trimData.R | 16 +++--- tests/testthat/test_generateSettings.R | 8 +-- tests/testthat/test_generateShell.R | 4 +- 8 files changed, 72 insertions(+), 68 deletions(-) diff --git a/R/checkColumn.R b/R/checkColumn.R index ff80d9fc..c6bb0db1 100644 --- a/R/checkColumn.R +++ b/R/checkColumn.R @@ -16,33 +16,33 @@ #' testSettings$filters[[1]]<-list(value_col="RACE",label="Race") #' testSettings$filters[[2]]<-list(value_col=NULL,label="No Column") #' testSettings$filters[[3]]<-list(value_col="NotAColumn",label="Invalid Column") -#' +#' #' #pass ($valid == TRUE) #' safetyGraphics:::checkColumn(key=list("id_col"), -#' settings=testSettings, adlbc) -#' +#' settings=testSettings, adlbc) +#' #' #pass #' safetyGraphics:::checkColumn(key=list("filters",1,"value_col"), -#' settings=testSettings, adlbc) -#' +#' settings=testSettings, adlbc) +#' #' #NULL column pass #' safetyGraphics:::checkColumn(key=list("filters",2,"value_col"), -#' settings=testSettings, adlbc) -#' +#' settings=testSettings, adlbc) +#' #' #invalid column fails #' safetyGraphics:::checkColumn(key=list("filters",3,"value_col"), -#' settings=testSettings, adlbc) +#' settings=testSettings, adlbc) #' @keywords internal checkColumn <- function(key, settings, data){ stopifnot(typeof(key)=="list",typeof(settings)=="list") - + current <- list(key=key) current$text_key <- paste( unlist(current$key), collapse='--') current$type <- "column" current$description <- "column parameter from setting setting found in data?" current$value <- getSettingValue(key=key,settings=settings) - if(is.na(current$value)||is.null(current$value)){ + if(is.null(current$value)){ current$value <- "--No Value Given--" current$valid <- TRUE current$message <- "" diff --git a/R/checkNumeric.R b/R/checkNumeric.R index 1dbe2983..588c95b4 100644 --- a/R/checkNumeric.R +++ b/R/checkNumeric.R @@ -10,11 +10,11 @@ #' @examples #' testSettings<-generateSettings(standard="AdAM") #' #pass ($valid == FALSE) -#' safetyGraphics:::checkNumeric(key=list("id_col"),settings=testSettings, data=adlbc) -#' +#' safetyGraphics:::checkNumeric(key=list("id_col"),settings=testSettings, data=adlbc) +#' #' #pass ($valid == TRUE) -#' safetyGraphics:::checkNumeric(key=list("value_col"),settings=testSettings, data=adlbc) -#' +#' safetyGraphics:::checkNumeric(key=list("value_col"),settings=testSettings, data=adlbc) +#' #' @keywords internal checkNumeric <- function(key, settings, data){ @@ -25,7 +25,7 @@ checkNumeric <- function(key, settings, data){ current$type <- "numeric" current$description <- "specified column is numeric?" current$value <- getSettingValue(key=key,settings=settings) - if(is.na(current$value)||is.null(current$value)){ + if(is.null(current$value)){ current$value <- "--No Value Given--" current$valid <- TRUE current$message <- "" diff --git a/R/generateSettings.R b/R/generateSettings.R index 7fd2e4cd..7a2963c7 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -1,80 +1,80 @@ #' Generate a settings object based on a data standard #' -#' This function returns a settings object for the eDish chart based on the specified data standard. +#' This function returns a settings object for the eDish chart based on the specified data standard. #' #' The function is designed to work with the SDTM and AdAM CDISC() standards for clinical trial data. Currently, eDish is the only chart supported. #' #' @param standard The data standard for which to create settings. Valid options are "SDTM", "AdAM" or "None". Default: \code{"None"}. #' @param charts The chart or chart(s) for which settings should be generated. Default: \code{NULL} (uses all available charts). -#' @param useDefaults Specifies whether default values from settingsMetadata should be included in the settings object. Default: \code{TRUE}. +#' @param useDefaults Specifies whether default values from settingsMetadata should be included in the settings object. Default: \code{TRUE}. #' @param partial Boolean for whether or not the standard is a partial standard. Default: \code{FALSE}. #' @param partial_keys Optional character vector of the matched settings if partial is TRUE. Settings should be identified using the text_key format described in ?settingsMetadata. Setting is ignored when partial is FALSE. Default: \code{NULL}. -#' @param custom_settings a tibble with text_key and customValue columns specifiying customizations to be applied to the settings object. Default: \code{NULL}. +#' @param custom_settings a tibble with text_key and customValue columns specifiying customizations to be applied to the settings object. Default: \code{NULL}. #' @return A list containing the appropriate settings for the selected chart -#' -#' @examples -#' -#' generateSettings(standard="SDTM") +#' +#' @examples +#' +#' generateSettings(standard="SDTM") #' generateSettings(standard="SdTm") #also ok #' generateSettings(standard="ADaM") #' pkeys<- c("id_col","measure_col","value_col") #' generateSettings(standard="adam", partial=TRUE, partial_keys=pkeys) -#' -#' generateSettings(standard="a different standard") +#' +#' generateSettings(standard="a different standard") #' #returns shell settings list with no data mapping -#' +#' #' \dontrun{ -#' generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. +#' generateSettings(standard="adam",chart="AEExplorer") #Throws error. Only eDish supported so far. #' } -#' +#' #' @importFrom dplyr "filter" full_join #' @importFrom stringr str_split #' @importFrom rlang .data -#' +#' #' @export generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, partial=FALSE, partial_keys=NULL, custom_settings=NULL){ - + # Check that partial_keys is supplied if partial is true if (is.null(partial_keys) & partial ) { stop("partial_keys must be supplied if the standard is partial") } - + # Coerce options to lowercase standard<-tolower(standard) if(!is.null(charts)){ - charts<-tolower(charts) + charts<-tolower(charts) } - + ############################################################################# - # get keys & default values for settings using a data standard (data and field mappings) + # get keys & default values for settings using a data standard (data and field mappings) ############################################################################ # Build a table of data mappings for the selected standard and partial settings standardList<-safetyGraphics::standardsMetadata%>%select(-.data$text_key)%>%names - + if(standard %in% standardList){ dataDefaults <- safetyGraphics::getSettingsMetadata( - charts = charts, + charts = charts, cols=c("text_key",standard,"setting_required") - ) %>% + ) %>% filter(.data$setting_required)%>% - select(-.data$setting_required)%>% + select(-.data$setting_required)%>% rename("dataDefault" = standard)%>% filter(.data$dataDefault != '') }else{ dataDefaults<-tibble(text_key=character(),dataDefault=character(), .rows=0) } - + if(partial){ - dataDefaults <-dataDefaults%>%filter(.data$text_key %in% partial_keys) + dataDefaults <-dataDefaults%>%filter(.data$text_key %in% partial_keys) } - + ############################################################################# - # get keys & default values for settings not using a data standard + # get keys & default values for settings not using a data standard ############################################################################# if(useDefaults){ otherDefaults <- safetyGraphics::getSettingsMetadata( - charts = charts, + charts = charts, filter = !.data$column_mapping & !.data$field_mapping, cols=c("text_key","default") )%>% @@ -82,13 +82,13 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par }else{ otherDefaults <- tibble(text_key=character(),otherDefault=character(), .rows=0) } - + ############################################################################# # merge all keys & default values ############################################################################# key_values <- full_join(dataDefaults, otherDefaults, by="text_key") key_values <- key_values %>% mutate(default=ifelse(is.na(.data$dataDefault),.data$otherDefault,.data$dataDefault)) - + ############################################################################# # Apply custom settings (if any) ############################################################################# @@ -97,24 +97,24 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par } else { key_values$customValue<-NA } - + key_values<-key_values %>% mutate(value=ifelse(is.na(.data$customValue), .data$default, .data$customValue)) - + ############################################################################# # create shell settings object ############################################################################# - shell<-generateShell(charts=charts) - + shell<-generateShell(charts=charts) + ######################################################################################### # populate the shell settings by looping through key_values and apply them to the shell ######################################################################################### for(row in 1:nrow(key_values)){ shell<-setSettingsValue( - settings = shell, - key = textKeysToList(key_values[row,"text_key"])[[1]], + settings = shell, + key = textKeysToList(key_values[row,"text_key"])[[1]], value = key_values[row, "value"][[1]] ) - } - + } + return(shell) } diff --git a/R/generateShell.R b/R/generateShell.R index e6ba68ba..f0dac6d7 100644 --- a/R/generateShell.R +++ b/R/generateShell.R @@ -24,7 +24,7 @@ generateShell <- function(charts=NULL){ for (i in 1:length(keys) ) { shell<-setSettingsValue( key=keys[[i]], - value=NA, #NA is prefered here since NULL deletes the element in the list + value=NULL, settings=shell, forceCreate=TRUE ) diff --git a/R/setSettingsValue.R b/R/setSettingsValue.R index cf8ce72d..e0cf4f53 100644 --- a/R/setSettingsValue.R +++ b/R/setSettingsValue.R @@ -34,7 +34,11 @@ setSettingsValue <- function(key, value, settings, forceCreate=FALSE){ firstKey <- key[[1]] if(length(key)==1){ - settings[[firstKey]]<-value + if(is.null(value)){ + settings[firstKey]<-list(NULL) + }else{ + settings[[firstKey]]<-value + } return(settings) }else{ settings[[firstKey]]<-setSettingsValue(settings = settings[[firstKey]],key = key[2:length(key)], value=value, forceCreate=forceCreate) diff --git a/R/trimData.R b/R/trimData.R index fb150005..b7e72c54 100644 --- a/R/trimData.R +++ b/R/trimData.R @@ -22,10 +22,10 @@ trimData <- function(data, settings, chart="edish"){ ## Remove columns not in settings ## col_names <- colnames(data) - + allKeys <- getSettingsMetadata(charts=chart, filter_expr = .data$column_mapping, cols = c("text_key","setting_type")) dataKeys <- allKeys %>% filter(.data$setting_type !="vector") %>% pull(.data$text_key) %>% textKeysToList() - + # Add items in vectors to list individually dataVectorKeys <- allKeys %>% filter(.data$setting_type =="vector") %>% pull(.data$text_key) %>% textKeysToList() for(key in dataVectorKeys){ @@ -37,12 +37,12 @@ trimData <- function(data, settings, chart="edish"){ sub <- current[[i]] if(typeof(sub)=="list"){ newKey[[1+length(newKey)]]<-"value_col" - } - dataKeys[[1+length(dataKeys)]]<-newKey + } + dataKeys[[1+length(dataKeys)]]<-newKey } } } - + settings_values <- map(dataKeys, function(x) {return(getSettingValue(x, settings))}) common_cols <- intersect(col_names,settings_values) @@ -51,10 +51,10 @@ trimData <- function(data, settings, chart="edish"){ ## Remove rows if baseline or analysisFlag is specified ## baselineSetting<-settings[['baseline']][['value_col']] - baselineMissing <- is.null(baselineSetting) || is.na(baselineSetting) + baselineMissing <- is.null(baselineSetting) analysisSetting<-settings[['analysisFlag']][['value_col']] - analysisMissing <- is.null(analysisSetting) || is.na(analysisSetting) - + analysisMissing <- is.null(analysisSetting) + if(!baselineMissing | !analysisMissing) { # Create Baseline String diff --git a/tests/testthat/test_generateSettings.R b/tests/testthat/test_generateSettings.R index 4bd49f3a..f99a356c 100644 --- a/tests/testthat/test_generateSettings.R +++ b/tests/testthat/test_generateSettings.R @@ -28,13 +28,13 @@ test_that("data mappings are null when setting=none, character otherwise",{ none_settings <- generateSettings(standard="None") for(text_key in data_setting_keys){ key<-textKeysToList(text_key)[[1]] - expect_equal(getSettingValue(settings=none_settings,key=key),NA) + expect_equal(getSettingValue(settings=none_settings,key=key),NULL) } other_settings <- generateSettings(standard="a different standard") for(text_key in data_setting_keys){ key<-textKeysToList(text_key)[[1]] - expect_equal(getSettingValue(settings=other_settings,key=key),NA) + expect_equal(getSettingValue(settings=other_settings,key=key),NULL) } sdtm_settings <- generateSettings(standard="SDTM") @@ -71,7 +71,7 @@ test_that("data mappings are null when setting=none, character otherwise",{ if (text_key %in% c("id_col","measure_col","measure_values--ALT")) { expect_is(getSettingValue(settings=partial_adam_settings,key=key),"character") } else { - expect_equal(getSettingValue(settings=partial_adam_settings,key=key),NA) + expect_equal(getSettingValue(settings=partial_adam_settings,key=key),NULL) } } @@ -92,7 +92,7 @@ test_that("data mappings are null when setting=none, character otherwise",{ #non data mappings are NA for(text_key in option_keys){ key<-textKeysToList(text_key)[[1]] - expect_equal(getSettingValue(settings=noDefaults,key=key),NA) + expect_equal(getSettingValue(settings=noDefaults,key=key),NULL) } #data mappings are filled as expected diff --git a/tests/testthat/test_generateShell.R b/tests/testthat/test_generateShell.R index c271a10b..f0dbbba7 100644 --- a/tests/testthat/test_generateShell.R +++ b/tests/testthat/test_generateShell.R @@ -5,8 +5,8 @@ default <- generateShell() test_that("a list with the expected properties and structure is returned by default",{ expect_type(default, "list") - expect_equal(default[["id_col"]],NA) - expect_equal(default[["measure_values"]][["ALT"]],NA) + expect_equal(default[["id_col"]],NULL) + expect_equal(default[["measure_values"]][["ALT"]],NULL) expect_null(default[["not_a_setting"]]) }) From 0f8a0cae5abd80a8e8a27a0ddd40339b91fca645 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:43:25 -0400 Subject: [PATCH 24/98] create safetyHistogram widget --- NAMESPACE | 3 + R/safetyHistogram.R | 145 +++ .../safety-histogram-2.2.2/safetyHistogram.js | 962 ++++++++++++++++++ inst/htmlwidgets/safetyHistogram.js | 36 + inst/htmlwidgets/safetyHistogram.yaml | 15 + man/safetyHistogram-shiny.Rd | 30 + man/safetyHistogram.Rd | 83 ++ 7 files changed, 1274 insertions(+) create mode 100644 R/safetyHistogram.R create mode 100644 inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js create mode 100644 inst/htmlwidgets/safetyHistogram.js create mode 100644 inst/htmlwidgets/safetyHistogram.yaml create mode 100644 man/safetyHistogram-shiny.Rd create mode 100644 man/safetyHistogram.Rd diff --git a/NAMESPACE b/NAMESPACE index c39c9361..c5514b8c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,7 +7,10 @@ export(generateSettings) export(getRequiredSettings) export(getSettingsMetadata) export(renderEDISH) +export(renderSafetyHistogram) export(safetyGraphicsApp) +export(safetyHistogram) +export(safetyHistogramOutput) export(validateSettings) import(DT) import(dplyr) diff --git a/R/safetyHistogram.R b/R/safetyHistogram.R new file mode 100644 index 00000000..03b383b8 --- /dev/null +++ b/R/safetyHistogram.R @@ -0,0 +1,145 @@ +#' Create a Safety Histogram widget +#' +#' This function creates a Safety Histogram using R htmlwidgets. +#' +#' @param data A data frame containing the labs data. Data must be structured as one record per study participant per time point per lab measure. +#' @param id_col Unique subject identifier variable name. Default: \code{"USUBJID"}. +#' @param value_col Lab result variable name. Default: \code{"STRESN"}. +#' @param measure_col Lab measure variable name. Default: \code{"TEST"}. +#' @param normal_col_low Lower limit of normal variable name. Default: \code{"STNRLO"}. +#' @param normal_col_high Upper limit of normal variable name. Default: \code{"STNRHI"}. +#' @param unit_col Unit of measure variable name. Default is \code{"STRESU"}. +#' @param filters 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. Default: \code{NULL}. +#' @param details An optional list of specifications for details listing. Each column to be added to details listing is a nested, named list (containing the variable name: "value_col" and associated label: "label") within the larger list. Default: \code{NULL}. +#' @param start_value Value of variable defined in \code{measure_col} to be rendered in the histogram when the widget loads. +#' @param missingValues Vector of values defining a missing \code{value_col}. Default is \code{c('','NA','N/A')}. +#' @param debug_js print settings in javascript before rendering chart. Default: \code{FALSE}. +#' @param settings Optional list of settings arguments to be converted to JSON using \code{jsonlite::toJSON(settings, auto_unbox = TRUE, dataframe = "rows", null = "null")}. If provided, all other function parameters are ignored. Default: \code{NULL}. +#' +#' @examples +#' \dontrun{ +#' +#' ## Create eDISH figure customized to user data +#'safetyHistogram(data=adlbc, +#' id_col = "USUBJID", +#' value_col = "AVAL", +#' measure_col = "PARAM", +#' normal_col_low = "A1LO", +#' normal_col_high = "A1HI", +#' unit_col = "PARAMCD") +#' +#' ## Create eDISH figure using a premade settings list +#' details_list <- list( +#' list(value_col = "TRTP", label = "Treatment"), +#' list(value_col = "SEX", label = "Sex"), +#' list(value_col = "AGEGR1", label = "Age group") +#' ) +#' +#' +#' filters_list <- list( +#' list(value_col = "TRTA", label = "Treatment"), +#' list(value_col = "SEX", label = "Sex"), +#' list(value_col = "RACE", label = "RACE"), +#' list(value_col = "AGEGR1", label = "Age group") +#' ) +#' +#' settingsl <- list(id_col = "USUBJID", +#' value_col = "AVAL", +#' measure_col = "PARAM", +#' unit_col = "PARAMCD", +#' normal_col_low = "A1LO", +#' normal_col_high = "A1HI", +#' details = details_list, +#' filters = filters_list) +#' +#' safetyHistogram(data=adlbc, settings = settingsl) +#' +#' } +#' +#' @import htmlwidgets +#' +#' @export +safetyHistogram <- function(data, + id_col = "USUBJID", + value_col = "STRESN", + measure_col = "TEST", + normal_col_low = "STNRLO", + normal_col_high = "STNRHI", + unit_col = "STRESU", + filters = NULL, + details = NULL, + start_value = NULL, + missingValues = c("","NA","N/A"), + debug_js = FALSE, + settings = NULL) { + + # forward options using rSettings + if (is.null(settings)){ + rSettings = list( + data = data, + settings = jsonlite::toJSON( + list( + id_col = id_col, + value_col = value_col, + measure_col = measure_col, + normal_col_low = normal_col_low, + normal_col_high = normal_col_high, + unit_col = unit_col, + filters = filters, + details = details, + start_value = start_value, + missingValues = missingValues, + debug_js = debug_js + ), + auto_unbox = TRUE, + null = "null" + ) + ) + } else{ + rSettings = list( + data = data, + settings = jsonlite::toJSON(settings, + auto_unbox = TRUE, + null = "null") + ) + } + + # create widget + htmlwidgets::createWidget( + name = 'safetyHistogram', + rSettings, + # width = width, + # height = height, + package = 'safetyGraphics', + sizingPolicy = htmlwidgets::sizingPolicy(viewer.suppress=TRUE, browser.external = TRUE) + + ) +} + +#' Shiny bindings for safetyHistogram +#' +#' Output and render functions for using safetyHistogram within Shiny +#' applications and interactive Rmd documents. +#' +#' @param outputId output variable to read from +#' @param width,height Must be a valid CSS unit (like \code{'100\%'}, +#' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a +#' string and have \code{'px'} appended. +#' @param expr An expression that generates a safetyHistogram +#' @param env The environment in which to evaluate \code{expr}. +#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This +#' is useful if you want to save an expression in a variable. +#' +#' @name safetyHistogram-shiny +#' +#' @export +safetyHistogramOutput <- function(outputId, width = '100%', height = '400px'){ + htmlwidgets::shinyWidgetOutput(outputId, 'safetyHistogram', width, height, package = 'safetyGraphics') +} + +#' @rdname safetyHistogram-shiny +#' @export +renderSafetyHistogram <- function(expr, env = parent.frame(), quoted = FALSE) { + if (!quoted) { expr <- substitute(expr) } # force quoted + htmlwidgets::shinyRenderWidget(expr, safetyHistogramOutput, env, quoted = TRUE) +} diff --git a/inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js b/inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js new file mode 100644 index 00000000..d1b2727d --- /dev/null +++ b/inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js @@ -0,0 +1,962 @@ +(function(global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' + ? (module.exports = factory(require('d3'), require('webcharts'))) + : typeof define === 'function' && define.amd + ? define(['d3', 'webcharts'], factory) + : (global.safetyHistogram = factory(global.d3, global.webCharts)); +})(this, function(d3, webcharts) { + 'use strict'; + + if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, 'assign', { + value: function assign(target, varArgs) { + if (target == null) { + // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { + // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + + return to; + }, + writable: true, + configurable: true + }); + } + + if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, 'find', { + value: function value(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, 'length')). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). + // d. If testResult is true, return kValue. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + // e. Increase k by 1. + k++; + } + + // 7. Return undefined. + return undefined; + } + }); + } + + if (!Array.prototype.findIndex) { + Object.defineProperty(Array.prototype, 'findIndex', { + value: function value(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). + // d. If testResult is true, return k. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return k; + } + // e. Increase k by 1. + k++; + } + + // 7. Return -1. + return -1; + } + }); + } + + Math.log10 = Math.log10 = + Math.log10 || + function(x) { + return Math.log(x) * Math.LOG10E; + }; + + var rendererSpecificSettings = { + //required variables + id_col: 'USUBJID', + measure_col: 'TEST', + unit_col: 'STRESU', + value_col: 'STRESN', + normal_col_low: 'STNRLO', + normal_col_high: 'STNRHI', + + //optional variables + filters: null, + details: null, + + //miscellaneous settings + start_value: null, + normal_range: true, + displayNormalRange: false + }; + + var webchartsSettings = { + x: { + type: 'linear', + column: null, // set in syncSettings() + label: null, // set in syncSettings() + domain: [null, null], // set in preprocess callback + format: null, // set in preprocess callback + bin: 25 + }, + y: { + type: 'linear', + column: null, + label: '# of Observations', + domain: [0, null], + format: '1d', + behavior: 'flex' + }, + marks: [ + { + per: [], // set in syncSettings() + type: 'bar', + summarizeY: 'count', + summarizeX: 'mean', + attributes: { 'fill-opacity': 0.75 } + } + ], + aspect: 3 + }; + + var defaultSettings = Object.assign({}, rendererSpecificSettings, webchartsSettings); + + //Replicate settings in multiple places in the settings object + function syncSettings(settings) { + settings.x.label = settings.start_value; + settings.x.column = settings.value_col; + settings.marks[0].per[0] = settings.value_col; + + if (!settings.normal_range) { + settings.normal_col_low = null; + settings.normal_col_high = null; + } + + //Define default details. + var defaultDetails = [{ value_col: settings.id_col, label: 'Subject Identifier' }]; + if (settings.filters) + settings.filters.forEach(function(filter) { + return defaultDetails.push({ + value_col: filter.value_col ? filter.value_col : filter, + label: filter.label + ? filter.label + : filter.value_col + ? filter.value_col + : filter + }); + }); + defaultDetails.push({ value_col: settings.value_col, label: 'Result' }); + if (settings.normal_col_low) + defaultDetails.push({ + value_col: settings.normal_col_low, + label: 'Lower Limit of Normal' + }); + if (settings.normal_col_high) + defaultDetails.push({ + value_col: settings.normal_col_high, + label: 'Upper Limit of Normal' + }); + + //If [settings.details] is not specified: + if (!settings.details) settings.details = defaultDetails; + else { + //If [settings.details] is specified: + //Allow user to specify an array of columns or an array of objects with a column property + //and optionally a column label. + settings.details.forEach(function(detail) { + if ( + defaultDetails + .map(function(d) { + return d.value_col; + }) + .indexOf(detail.value_col ? detail.value_col : detail) === -1 + ) + defaultDetails.push({ + value_col: detail.value_col ? detail.value_col : detail, + label: detail.label + ? detail.label + : detail.value_col + ? detail.value_col + : detail + }); + }); + settings.details = defaultDetails; + } + + return settings; + } + + //Map values from settings to control inputs + function syncControlInputs(settings) { + var defaultControls = [ + { + type: 'subsetter', + label: 'Measure', + value_col: settings.measure_col, + start: settings.start_value + }, + { + type: 'checkbox', + label: 'Normal Range', + option: 'displayNormalRange' + }, + { + type: 'number', + label: 'Lower Limit', + option: 'x.domain[0]', + require: true + }, + { + type: 'number', + label: 'Upper Limit', + option: 'x.domain[1]', + require: true + } + ]; + + if (Array.isArray(settings.filters) && settings.filters.length > 0) { + var otherFilters = settings.filters.map(function(filter) { + var filterObject = { + type: 'subsetter', + value_col: filter.value_col || filter, + label: filter.label || filter.value_col || filter + }; + return filterObject; + }); + + return defaultControls.concat(otherFilters); + } else return defaultControls; + } + + function countParticipants() { + var _this = this; + + this.populationCount = d3 + .set( + this.raw_data.map(function(d) { + return d[_this.config.id_col]; + }) + ) + .values().length; + } + + function cleanData() { + var _this = this; + + //Remove missing and non-numeric data. + var preclean = this.raw_data; + var clean = this.raw_data.filter(function(d) { + return /^-?[0-9.]+$/.test(d[_this.config.value_col]); + }); + var nPreclean = preclean.length; + var nClean = clean.length; + var nRemoved = nPreclean - nClean; + + //Warn user of removed records. + if (nRemoved > 0) + console.warn( + nRemoved + + ' missing or non-numeric result' + + (nRemoved > 1 ? 's have' : ' has') + + ' been removed.' + ); + + //Preserve cleaned data. + this.raw_data = clean; + + //Attach array of continuous measures to chart object. + this.measures = d3 + .set( + this.raw_data.map(function(d) { + return d[_this.config.measure_col]; + }) + ) + .values() + .sort(); + } + + function addVariables() { + var _this = this; + + this.raw_data.forEach(function(d) { + d[_this.config.measure_col] = d[_this.config.measure_col].trim(); + }); + } + + function checkFilters() { + var _this = this; + + this.controls.config.inputs = this.controls.config.inputs.filter(function(input) { + if (input.type != 'subsetter') { + return true; + } else if (!_this.raw_data[0].hasOwnProperty(input.value_col)) { + console.warn( + 'The [ ' + + input.label + + ' ] filter has been removed because the variable does not exist.' + ); + } else { + var levels = d3 + .set( + _this.raw_data.map(function(d) { + return d[input.value_col]; + }) + ) + .values(); + + if (levels.length === 1) + console.warn( + 'The [ ' + + input.label + + ' ] filter has been removed because the variable has only one level.' + ); + + return levels.length > 1; + } + }); + } + + function setInitialMeasure() { + this.controls.config.inputs.find(function(input) { + return input.label === 'Measure'; + }).start = + this.config.start_value && this.measures.indexOf(this.config.start_value) > -1 + ? this.config.start_value + : this.measures[0]; + } + + function onInit() { + // 1. Count total participants prior to data cleaning. + countParticipants.call(this); + + // 2. Drop missing values and remove measures with any non-numeric results. + cleanData.call(this); + + // 3a Define additional variables. + addVariables.call(this); + + // 3b Remove filters for nonexistent or single-level variables. + checkFilters.call(this); + + // 3c Choose the start value for the Test filter + setInitialMeasure.call(this); + } + + function addXdomainResetButton() { + var _this = this; + + //Add x-domain reset button container. + var resetContainer = this.controls.wrap + .insert('div', '.control-group:nth-child(3)') + .classed('control-group x-axis', true) + .datum({ + type: 'button', + option: 'x.domain', + label: 'x-axis:' + }); + + //Add label. + resetContainer + .append('span') + .attr('class', 'wc-control-label') + .style('text-align', 'right') + .text('X-axis:'); + + //Add button. + resetContainer + .append('button') + .text('Reset Limits') + .on('click', function() { + _this.config.x.domain = _this.measure_domain; + + _this.controls.wrap + .selectAll('.control-group') + .filter(function(f) { + return f.option === 'x.domain[0]'; + }) + .select('input') + .property('value', _this.config.x.domain[0]); + + _this.controls.wrap + .selectAll('.control-group') + .filter(function(f) { + return f.option === 'x.domain[1]'; + }) + .select('input') + .property('value', _this.config.x.domain[1]); + + _this.draw(); + }); + } + + function classXaxisLimitControls() { + this.controls.wrap + .selectAll('.control-group') + .filter(function(d) { + return ['Lower Limit', 'Upper Limit'].indexOf(d.label) > -1; + }) + .classed('x-axis', true); + } + + function addPopulationCountContainer() { + this.controls.wrap + .append('div') + .attr('id', 'populationCount') + .style('font-style', 'italic'); + } + + function addFootnoteContainer() { + this.wrap + .insert('p', '.wc-chart') + .attr('class', 'annote') + .text('Click a bar for details.'); + } + + function onLayout() { + //Add button that resets x-domain. + addXdomainResetButton.call(this); + + //Add x-axis class to x-axis limit controls. + classXaxisLimitControls.call(this); + + //Add container for population count. + addPopulationCountContainer.call(this); + + //Add container for footnote. + addFootnoteContainer.call(this); + } + + function getCurrentMeasure() { + var _this = this; + + this.previousMeasure = this.currentMeasure; + this.currentMeasure = this.filters.find(function(filter) { + return filter.col === _this.config.measure_col; + }).val; + } + + function defineMeasureData() { + var _this = this; + + this.measure_data = this.raw_data.filter(function(d) { + return d[_this.config.measure_col] === _this.currentMeasure; + }); + this.measure_domain = d3.extent(this.measure_data, function(d) { + return +d[_this.config.value_col]; + }); + } + + function setXdomain() { + if (this.currentMeasure !== this.previousMeasure) + // new measure + this.config.x.domain = this.measure_domain; + else if (this.config.x.domain[0] > this.config.x.domain[1]) + // invalid domain + this.config.x.domain.reverse(); + else if (this.config.x.domain[0] === this.config.x.domain[1]) + // domain with zero range + this.config.x.domain = this.config.x.domain.map(function(d, i) { + return i === 0 ? d - d * 0.01 : d + d * 0.01; + }); + } + + function setXaxisLabel() { + this.config.x.label = + this.currentMeasure + + (this.config.unit_col && this.measure_data[0][this.config.unit_col] + ? ' (' + this.measure_data[0][this.config.unit_col] + ')' + : ''); + } + + function setXprecision() { + var _this = this; + + //Calculate range of current measure and the log10 of the range to choose an appropriate precision. + this.config.x.range = this.config.x.domain[1] - this.config.x.domain[0]; + this.config.x.log10range = Math.log10(this.config.x.range); + this.config.x.roundedLog10range = Math.round(this.config.x.log10range); + this.config.x.precision1 = -1 * (this.config.x.roundedLog10range - 1); + this.config.x.precision2 = -1 * (this.config.x.roundedLog10range - 2); + + //Define the format of the x-axis tick labels and x-domain controls. + this.config.x.format = + this.config.x.log10range > 0.5 ? '1f' : '.' + this.config.x.precision1 + 'f'; + this.config.x.d3_format = d3.format(this.config.x.format); + this.config.x.formatted_domain = this.config.x.domain.map(function(d) { + return _this.config.x.d3_format(d); + }); + + //Define the bin format: one less than the x-axis format. + this.config.x.format1 = + this.config.x.log10range > 5 ? '1f' : '.' + this.config.x.precision2 + 'f'; + this.config.x.d3_format1 = d3.format(this.config.x.format1); + } + + function updateXaxisLimitControls() { + //Update x-axis limit controls. + this.controls.wrap + .selectAll('.control-group') + .filter(function(f) { + return f.option === 'x.domain[0]'; + }) + .select('input') + .property('value', this.config.x.formatted_domain[0]); + this.controls.wrap + .selectAll('.control-group') + .filter(function(f) { + return f.option === 'x.domain[1]'; + }) + .select('input') + .property('value', this.config.x.formatted_domain[1]); + } + + function updateXaxisResetButton() { + //Update tooltip of x-axis domain reset button. + if (this.currentMeasure !== this.previousMeasure) + this.controls.wrap + .selectAll('.x-axis') + .property( + 'title', + 'Initial Limits: [' + + this.config.x.domain[0] + + ' - ' + + this.config.x.domain[1] + + ']' + ); + } + + function onPreprocess() { + // 1. Capture currently selected measure. + getCurrentMeasure.call(this); + + // 2. Filter data on currently selected measure. + defineMeasureData.call(this); + + // 3a Set x-domain given currently selected measure. + setXdomain.call(this); + + // 3b Set x-axis label to current measure. + setXaxisLabel.call(this); + + // 4a Define precision of measure. + setXprecision.call(this); + + // 4b Update x-axis reset button when measure changes. + updateXaxisResetButton.call(this); + + // 4c Update x-axis limit controls to match y-axis domain. + updateXaxisLimitControls.call(this); + } + + function onDatatransform() {} + + // Takes a webcharts object creates a text annotation giving the + + function updateParticipantCount(chart, selector, id_unit) { + //count the number of unique ids in the current chart and calculate the percentage + var currentObs = d3 + .set( + chart.filtered_data.map(function(d) { + return d[chart.config.id_col]; + }) + ) + .values().length; + var percentage = d3.format('0.1%')(currentObs / chart.populationCount); + + //clear the annotation + var annotation = d3.select(selector); + d3.select(selector) + .selectAll('*') + .remove(); + + //update the annotation + var units = id_unit ? ' ' + id_unit : ' participant(s)'; + annotation.text( + '\n' + + currentObs + + ' of ' + + chart.populationCount + + units + + ' shown (' + + percentage + + ')' + ); + } + + function resetRenderer() { + //Reset listing. + this.listing.draw([]); + this.listing.wrap.selectAll('*').style('display', 'none'); + + //Reset footnote. + this.wrap + .select('.annote') + .classed('tableTitle', false) + .text('Click a bar for details.'); + + //Reset bar highlighting. + delete this.highlightedBin; + this.svg.selectAll('.bar').attr('opacity', 1); + } + + function onDraw() { + //Annotate population count. This function is called on draw() so that it can access the + //filtered data, i.e. the data with the current filters applied. However the filtered data is + //mark-specific, which could cause issues in other scenarios with mark-specific filters via the + //marks.[].values setting. chart.filtered_data is set to the last mark data defined rather + //than the full data with filters applied, irrespective of the mark-specific filters. + updateParticipantCount(this, '#populationCount'); + + //Reset chart and listing. Doesn't really need to be called on draw() but whatever. + resetRenderer.call(this); + } + + function handleSingleObservation() { + this.svg.select('#custom-bin').remove(); + if (this.current_data.length === 1) { + var datum = this.current_data[0]; + this.svg + .append('g') + .classed('bar-group', true) + .attr('id', 'custom-bin') + .append('rect') + .data([datum]) + .classed('wc-data-mark bar', true) + .attr({ + y: 0, + height: this.plot_height, + 'shape-rendering': 'crispEdges', + stroke: 'rgb(102,194,165)', + fill: 'rgb(102,194,165)', + 'fill-opacity': '0.75', + width: this.x(datum.values.x * 1.01) - this.x(datum.values.x * 0.99), + x: this.x(datum.values.x * 0.99) + }); + } + } + + function addBinClickListener() { + var chart = this; + var config = this.config; + var bins = this.svg.selectAll('.bar'); + var footnote = this.wrap.select('.annote'); + + bins.style('cursor', 'pointer') + .on('click', function(d) { + chart.highlightedBin = d.key; + //Update footnote. + footnote + .classed('tableTitle', true) + .text( + 'Table displays ' + + d.values.raw.length + + ' records with ' + + (chart.filtered_data[0][config.measure_col] + ' values from ') + + (chart.config.x.d3_format1(d.rangeLow) + + ' to ' + + chart.config.x.d3_format1(d.rangeHigh)) + + (config.unit_col ? ' ' + chart.filtered_data[0][config.unit_col] : '') + + '. Click outside a bar to remove details.' + ); + + //Draw listing. + chart.listing.draw(d.values.raw); + chart.listing.wrap.selectAll('*').style('display', null); + + //Reduce bin opacity and highlight selected bin. + bins.attr('fill-opacity', 0.5); + d3.select(this).attr('fill-opacity', 1); + }) + .on('mouseover', function(d) { + //Update footnote. + if (footnote.classed('tableTitle') === false) + footnote.text( + d.values.raw.length + + ' records with ' + + (chart.filtered_data[0][config.measure_col] + ' values from ') + + (chart.config.x.d3_format1(d.rangeLow) + + ' to ' + + chart.config.x.d3_format1(d.rangeHigh)) + + (config.unit_col ? ' ' + chart.filtered_data[0][config.unit_col] : '') + ); + }) + .on('mouseout', function(d) { + //Update footnote. + if (footnote.classed('tableTitle') === false) + footnote.text('Click a bar for details.'); + }); + } + + function drawNormalRanges() { + var chart = this; + var config = this.config; + var normalRangeControl = this.controls.wrap.selectAll('.control-group').filter(function(d) { + return d.label === 'Normal Range'; + }); + + if (config.normal_range) { + if (chart.config.displayNormalRange) drawNormalRanges(chart); + else chart.wrap.selectAll('.normalRange').remove(); + + normalRangeControl.on('change', function() { + chart.config.displayNormalRange = d3 + .select(this) + .select('input') + .property('checked'); + + if (chart.config.displayNormalRange) drawNormalRanges(chart); + else chart.wrap.selectAll('.normalRange').remove(); + }); + } else normalRangeControl.style('display', 'none'); + + function drawNormalRanges() { + //Clear normal ranges. + var canvas = chart.svg; + canvas.selectAll('.normalRange').remove(); + + //Capture distinct normal ranges in filtered data. + var normalRanges = d3 + .nest() + .key(function(d) { + return d[chart.config.normal_col_low] + ',' + d[chart.config.normal_col_high]; + }) // set key to comma-delimited normal range + .rollup(function(d) { + return d.length; + }) + .entries(chart.filtered_data); + var currentRange = d3.extent(chart.filtered_data, function(d) { + return +d[chart.config.value_col]; + }); + //Sort normal ranges so larger normal ranges plot beneath smaller normal ranges. + normalRanges.sort(function(a, b) { + var a_lo = a.key.split(',')[0]; + var a_hi = a.key.split(',')[1]; + var b_lo = b.key.split(',')[0]; + var b_hi = b.key.split(',')[1]; + return a_lo <= b_lo && a_hi >= b_hi + ? 2 // lesser minimum and greater maximum + : a_lo >= b_lo && a_hi <= b_hi + ? -2 // greater minimum and lesser maximum + : a_lo <= b_lo && a_hi <= b_hi + ? 1 // lesser minimum and lesser maximum + : a_lo >= b_lo && a_hi >= b_hi + ? -1 // greater minimum and greater maximum + : 1; + }); + //Add divs to chart for each normal range. + canvas + .selectAll('.normalRange rect') + .data(normalRanges) + .enter() + .insert('rect', ':first-child') + .attr({ + class: 'normalRange', + x: function x(d) { + return chart.x(Math.max(+d.key.split(',')[0], currentRange[0])); + }, // set x to range low + y: 0, + width: function width(d) { + return Math.min( + chart.plot_width - + chart.x(Math.max(+d.key.split(',')[0], currentRange[0])), // chart width - range low + + chart.x(+d.key.split(',')[1]) - + chart.x(Math.max(+d.key.split(',')[0], currentRange[0])) + ); + }, // range high - range low + + height: chart.plot_height + }) + .style({ + stroke: 'black', + fill: 'black', + 'stroke-opacity': function strokeOpacity(d) { + return (d.values / chart.filtered_data.length) * 0.75; + }, // opacity as a function of fraction of records with the given normal range + 'fill-opacity': function fillOpacity(d) { + return (d.values / chart.filtered_data.length) * 0.5; + } + }) // opacity as a function of fraction of records with the given normal range + .append('title') + .text(function(d) { + return ( + 'Normal range: ' + + d.key.split(',')[0] + + '-' + + d.key.split(',')[1] + + (chart.config.unit_col + ? '' + chart.filtered_data[0][chart.config.unit_col] + : '') + + (' (' + + d3.format('%')(d.values / chart.filtered_data.length) + + ' of records)') + ); + }); + } + } + + function addClearListing() { + var chart = this; + var footnote = this.wrap.select('.annote'); + this.wrap.selectAll('.overlay, .normalRange').on('click', function() { + delete chart.highlightedBin; + chart.listing.draw([]); + chart.listing.wrap.selectAll('*').style('display', 'none'); + chart.svg.selectAll('.bar').attr('fill-opacity', 0.75); + + if (footnote.classed('tableTitle')) + footnote.classed('tableTitle', false).text('Click a bar for details.'); + }); + } + + function maintainBinHighlighting() { + var _this = this; + + if (this.highlightedBin) + this.svg.selectAll('.bar').attr('fill-opacity', function(d) { + return d.key !== _this.highlightedBin ? 0.5 : 1; + }); + } + + function hideDuplicateXaxisTickLabels() { + this.svg.selectAll('.x.axis .tick').each(function(d, i) { + var tick = d3.select(this); + var value = +d; + var text = +tick.select('text').text(); + tick.style('display', value === text ? 'block' : 'none'); + }); + } + + function onResize() { + //Draw custom bin for single observation subsets. + handleSingleObservation.call(this); + + //Display data listing on bin click. + addBinClickListener.call(this); + + //Visualize normal ranges. + drawNormalRanges.call(this); + + //Clear listing when clicking outside bins. + addClearListing.call(this); + + //Keep highlighted bin highlighted on resize. + maintainBinHighlighting.call(this); + + //Hide duplicate x-axis tick labels (d3 sometimes draws more ticks than the precision allows). + hideDuplicateXaxisTickLabels.call(this); + } + + function onDestroy() {} + + //polyfills + + function safetyHistogram(element, settings) { + //Define chart. + var mergedSettings = Object.assign({}, defaultSettings, settings); + var syncedSettings = syncSettings(mergedSettings); + var syncedControlInputs = syncControlInputs(syncedSettings); + var controls = webcharts.createControls(element, { + location: 'top', + inputs: syncedControlInputs + }); + var chart = webcharts.createChart(element, syncedSettings, controls); + + //Define chart callbacks. + chart.on('init', onInit); + chart.on('layout', onLayout); + chart.on('preprocess', onPreprocess); + chart.on('datatransform', onDatatransform); + chart.on('draw', onDraw); + chart.on('resize', onResize); + chart.on('destroy', onDestroy); + + //Define listing + var listingSettings = { + cols: syncedSettings.details.map(function(detail) { + return detail.value_col; + }), + headers: syncedSettings.details.map(function(detail) { + return detail.label; + }), + searchable: syncedSettings.searchable, + sortable: syncedSettings.sortable, + pagination: syncedSettings.pagination, + exportable: syncedSettings.exportable + }; + var listing = webcharts.createTable(element, listingSettings); + + //Attach listing to chart. + chart.listing = listing; + + //Initialize listing and hide initially. + chart.listing.init([]); + chart.listing.wrap.selectAll('*').style('display', 'none'); + + return chart; + } + + return safetyHistogram; +}); diff --git a/inst/htmlwidgets/safetyHistogram.js b/inst/htmlwidgets/safetyHistogram.js new file mode 100644 index 00000000..fed8b913 --- /dev/null +++ b/inst/htmlwidgets/safetyHistogram.js @@ -0,0 +1,36 @@ +HTMLWidgets.widget({ + + name: "safetyHistogram", + + type: "output", + + factory: function(el, width, height) { + + // TODO: define shared variables for this instance + + return { + + renderValue: function(rSettings) { + el.innerHTML = "
"; + var settings = rSettings.settings; + + if(settings.debug_js){ + console.log("R settings:") + console.log(rSettings); + } + + settings.max_width = 600; + rSettings.data = HTMLWidgets.dataframeToD3(rSettings.data); + safetyHistogram(".safetyhistogram", settings).init(rSettings.data); + + }, + + resize: function(width, height) { + + // TODO: code to re-render the widget with a new size + + } + + }; + } +}); \ No newline at end of file diff --git a/inst/htmlwidgets/safetyHistogram.yaml b/inst/htmlwidgets/safetyHistogram.yaml new file mode 100644 index 00000000..2f678ba6 --- /dev/null +++ b/inst/htmlwidgets/safetyHistogram.yaml @@ -0,0 +1,15 @@ +dependencies: + - name: d3 + version: 3.5.17 + src: htmlwidgets/lib/d3-3.5.17 + script: d3.v3.min.js + - name: webcharts + version: 1.11.3 + src: htmlwidgets/lib/webcharts-1.11.3 + script: webcharts.js + stylesheet: webcharts.css + - name: safety-histogram + version: 2.2.2 + src: htmlwidgets/lib/safety-histogram-2.2.2 + script: safetyHistogram.js + diff --git a/man/safetyHistogram-shiny.Rd b/man/safetyHistogram-shiny.Rd new file mode 100644 index 00000000..a7daebb6 --- /dev/null +++ b/man/safetyHistogram-shiny.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/safetyHistogram.R +\name{safetyHistogram-shiny} +\alias{safetyHistogram-shiny} +\alias{safetyHistogramOutput} +\alias{renderSafetyHistogram} +\title{Shiny bindings for safetyHistogram} +\usage{ +safetyHistogramOutput(outputId, width = "100\%", height = "400px") + +renderSafetyHistogram(expr, env = parent.frame(), quoted = FALSE) +} +\arguments{ +\item{outputId}{output variable to read from} + +\item{width, height}{Must be a valid CSS unit (like \code{'100\%'}, +\code{'400px'}, \code{'auto'}) or a number, which will be coerced to a +string and have \code{'px'} appended.} + +\item{expr}{An expression that generates a safetyHistogram} + +\item{env}{The environment in which to evaluate \code{expr}.} + +\item{quoted}{Is \code{expr} a quoted expression (with \code{quote()})? This +is useful if you want to save an expression in a variable.} +} +\description{ +Output and render functions for using safetyHistogram within Shiny +applications and interactive Rmd documents. +} diff --git a/man/safetyHistogram.Rd b/man/safetyHistogram.Rd new file mode 100644 index 00000000..e97035f6 --- /dev/null +++ b/man/safetyHistogram.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/safetyHistogram.R +\name{safetyHistogram} +\alias{safetyHistogram} +\title{Create a Safety Histogram widget} +\usage{ +safetyHistogram(data, id_col = "USUBJID", value_col = "STRESN", + measure_col = "TEST", normal_col_low = "STNRLO", + normal_col_high = "STNRHI", unit_col = "STRESU", filters = NULL, + details = NULL, start_value = NULL, missingValues = c("", "NA", + "N/A"), debug_js = FALSE, settings = NULL) +} +\arguments{ +\item{data}{A data frame containing the labs data. Data must be structured as one record per study participant per time point per lab measure.} + +\item{id_col}{Unique subject identifier variable name. Default: \code{"USUBJID"}.} + +\item{value_col}{Lab result variable name. Default: \code{"STRESN"}.} + +\item{measure_col}{Lab measure variable name. Default: \code{"TEST"}.} + +\item{normal_col_low}{Lower limit of normal variable name. Default: \code{"STNRLO"}.} + +\item{normal_col_high}{Upper limit of normal variable name. Default: \code{"STNRHI"}.} + +\item{unit_col}{Unit of measure variable name. Default is \code{"STRESU"}.} + +\item{filters}{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. Default: \code{NULL}.} + +\item{details}{An optional list of specifications for details listing. Each column to be added to details listing is a nested, named list (containing the variable name: "value_col" and associated label: "label") within the larger list. Default: \code{NULL}.} + +\item{start_value}{Value of variable defined in \code{measure_col} to be rendered in the histogram when the widget loads.} + +\item{missingValues}{Vector of values defining a missing \code{value_col}. Default is \code{c('','NA','N/A')}.} + +\item{debug_js}{print settings in javascript before rendering chart. Default: \code{FALSE}.} + +\item{settings}{Optional list of settings arguments to be converted to JSON using \code{jsonlite::toJSON(settings, auto_unbox = TRUE, dataframe = "rows", null = "null")}. If provided, all other function parameters are ignored. Default: \code{NULL}.} +} +\description{ +This function creates a Safety Histogram using R htmlwidgets. +} +\examples{ +\dontrun{ + +## Create eDISH figure customized to user data +safetyHistogram(data=adlbc, + id_col = "USUBJID", + value_col = "AVAL", + measure_col = "PARAM", + normal_col_low = "A1LO", + normal_col_high = "A1HI", + unit_col = "PARAMCD") + +## Create eDISH figure using a premade settings list +details_list <- list( + list(value_col = "TRTP", label = "Treatment"), + list(value_col = "SEX", label = "Sex"), + list(value_col = "AGEGR1", label = "Age group") +) + + +filters_list <- list( + list(value_col = "TRTA", label = "Treatment"), + list(value_col = "SEX", label = "Sex"), + list(value_col = "RACE", label = "RACE"), + list(value_col = "AGEGR1", label = "Age group") +) + +settingsl <- list(id_col = "USUBJID", + value_col = "AVAL", + measure_col = "PARAM", + unit_col = "PARAMCD", + normal_col_low = "A1LO", + normal_col_high = "A1HI", + details = details_list, + filters = filters_list) + +safetyHistogram(data=adlbc, settings = settingsl) + +} + +} From 1c5591a4e10f7abd90fd3cab6dd8bc64514a5c6a Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:52:42 -0400 Subject: [PATCH 25/98] update metadata per safetyHistogram addition --- data-raw/csv_to_rda.R | 9 ++++-- data-raw/defaults.Rds | Bin 0 -> 551 bytes data-raw/defaults.rda | Bin 509 -> 0 bytes data-raw/generateDefaults.R | 7 +++-- data-raw/settingsMetadata.csv | 55 +++++++++++++++++---------------- data-raw/standardsMetadata.csv | 5 ++- data/settingsMetadata.rda | Bin 1964 -> 2015 bytes data/standardsMetadata.rda | Bin 699 -> 731 bytes 8 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 data-raw/defaults.Rds delete mode 100644 data-raw/defaults.rda diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index 07dfc3cb..cd77239c 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -1,14 +1,17 @@ library(usethis) +library(dplyr) ablbc <- read.csv("data-raw/adlbc.csv") usethis::use_data(adlbc, overwrite = TRUE) -partialSettingsMetadata <- read.csv("data-raw/settingsMetadata.csv") +partialSettingsMetadata <- read.csv("data-raw/settingsMetadata.csv", stringsAsFactors = FALSE) #merge defaults on to settingsMetadata -defaults <- readRDS("data-raw/defaults.rda") #why is this not working... grrrr +defaults <- readRDS("data-raw/defaults.Rds") #why is this not working... grrrr -settingsMetadata <- merge(partialSettingsMetadata, defaults, by="text_key") + +settingsMetadata <- dplyr::full_join(partialSettingsMetadata, defaults, by="text_key") +#settingsMetadata <- merge(partialSettingsMetadata, defaults, by="text_key") usethis::use_data(settingsMetadata, overwrite = TRUE) diff --git a/data-raw/defaults.Rds b/data-raw/defaults.Rds new file mode 100644 index 0000000000000000000000000000000000000000..ee90d6b78360aeeb81bb09dd3cbf7141cae48a07 GIT binary patch literal 551 zcmV+?0@(c@iwFP!000002BlL?kJB&^^(Jh0yX=;VB5~p1KhSdLfG7w~XoYA%+@fjh zrWTG}`D44`#6RLs@jEEvG;XtD_e3Sv@tb*L`_1#W3qnXj($j<_DSoDtW=+T##|!d^ z3~?R^mKR#KtKkhu&pW{5ii7p03r!-_kuv`kor_;VV z<6bCIo`V8eTVX#)DF3BByC)o}!`obMoY3k3)IH#Gf2XZb>>$a=8rZBwK@nEkQS7B);5eJnxvFV_<`4x)YIvcmiYo?9D`_weqgqiH3OcPN z#m;!i=#tYytCcVnCioyUt+h8)n-Y}x(ekYWtYxsohKC+w*2d_9Gki#P);s=Sk$rx@ z_|nSTm6b%*uIEv;>z8vxlMekM#0mt^Vo+002?i5`q8# literal 0 HcmV?d00001 diff --git a/data-raw/defaults.rda b/data-raw/defaults.rda deleted file mode 100644 index 018b5ccd81d7422b32c977145f70c2c90809af02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 509 zcmV7HYouPzsqDk!^}dYMN*h~)3Y+K3 zbR+s=f>yGuM)=D4miHX`d*_i(`U!A30D6KvDWvkkx)@Me(>8&R$Yu{tD5=GEJMYeV zM#YrZph5Lcx=#wqf74z)K*!YOZEhMbjXnYW47fU;sgs(YTC%da1q{lMhl%1vl(jxq zbCu~b>6M5v9at@OnTcB;4m((bkU#%1LJcze4wg&DEz>*0q<02MgY3t^WQ3BFM@Djm zWAXkSiBB;2VjQb+eu!HS^Rd*gkI;s8zNJ|u9hDmU1|j8C^(QV?gV$&9pbIrN)+^ z<&QDndB9p0OEe;4KOCaWMuv=rnhhFgXlMWd>NcjBAZU7x z0NS3R!x5&M4FG5W003w*4Kxh^8Z-fjXbu>SG|*@RKmY&(L6B*nXaLcm3`0O9M1rTL zo}|;%+L;^GHj_uFGz|a%&;S4c002c1H3>6Dko65T4GkI(PyiYL02%-TKr{&w0tf`q z(3+V~$uemknW|{@Jf?v4Jx8d}X}wKPP#FMiOazEF#7Bw`asl%(8(5SLAZ$tMATT(a zZxJH2mL}*Zi6Q3*L^LKegnFP4p{B&^pc0sa)tzjtJt#`CqlINvf2Lod)|C$IAsUgE zNl6(EHpU3_*h_b|KB|cV#fyyhe&0mU0!IduVm*djB7Y~YZrK=K5FCF?CC&!S&VtvW zJv5W{+VZ6WC0PiJD>H$DCla4?wn)B<^c|1r#G8{{z2EIAsCeaVI(PnCQx04!Druc` zx^)n%D#W*nDx^`#EwSOG%Z&sHWkNuh0v9oxEgEMM3UQ$dScqsxRe}oxhXU1L8Lgm* zNP-}N0fA`#YfxrNlJX3(uA~N>WkYdHYd2ogTC2KMv#qPX+{~)Q;bQaAC*-JEHBuI| zp z>>Wx8$_<^JGeK6CB{DDGvA=8@3N=vCA&>S46`)j^l;qyIMjte{@|i5`k}GRanBbWz zR#FVfYB0eZ*0Qppe6>@emeCShXo|9S3=EnS4ja%|3niBDP?Z9PgA^+FUV=8b znomB)0*GRHNOg6ZZUhxbg}{ODD{dR&d&>c$21OiNni>Hkj{27;Lku;%aojTk{!J|= z8*Emk)$td`$rp8o#c0=qy#y|cI&oeidu~xis%en zMKBXYL?jR9bj^~@6|P4DrD@HJB^OwOs%5PrLu2?}AV1R>G={)!DkX^0R-w*)x*|fy zw9MN;qvl4Fb%6~WbuP4|z906>E@HYfP5S#rjA*k$Ht0zhm=VgIG5a9Vu6JJVVmuYh z>^iP$B0h7SXM}U*YP_u1{LuotH6!~jrjkULFkE+mK0h^A0WYe!p$tfVJ4h(>rXDF7 zq=pAQ=_Q56l8lhMcu9fIsZD$k38%lc*fK@!4SEf&inB9kj*2D995(D=_G%|a+Zc6V z+Uq;)wP$ohsX>q6bL3Pch9ziO6-q*xk~BQJ&#q^}H)(PDs4 z=?2nMVoiysA&ZHPU1oXRms_tsq;#R}fEU_lK`AA$Y7jLnF<_u>8LOG`Fr%z6oo%{G zzL#rSuZrvm~C@}s6j0=G2bMj-_$CuQE|mFXO_<)j3X*R zWkN_x5&(k(-WA7HM2rdxu?+JsmME>!bfjSmxfnw-$hB`YglnS==9kt9D23C4mS|6O zC<29su7&Y)NERj_S&FiVQuw4Ks4NX~Lox=G1E#@lVNw$Pdw#+qVo#uZ5bWxm3o zMHZ$xMvzSrPGH|$l4B{9Bv(0alD!RNsc{9;=aN`VE0}0BdYbt&$z2!`xra8ka=9`f zj!8*X6I7h0=7=IghMOeR8X3|tQP#G?>pj+(vlDxvDH7ym%5r(VFEGqhM%J!v8^II> z3%pQAAu~%&G%0CNpv}r?M%USsc&NQMI8Dqunz^PatIWQNTxzM9QN}4D8g$fvRXqVz03f3 zBS2C0oq3?L65C+Z%b^|Jb3%T1l#=7KWWAh<6qY6iX xg$mLR32WSOPQL|6l;WhV5OA9$zPNinNJC;hP=P8eWhj5e+>uTcBrA7ee*l2TcM$*p literal 1964 zcmV;d2UGY$T4*^jL0KkKSsyQxUjPO0fB*mg|My7ekQ4u>U*Nz0|M0*NfIt8MfCvBr z7ytl(00H0#Uo;d1RKh3#0000QB=t2VgZvwBh+c227^OPntF{i9;4KHfNf7u zN)bGi^(pv`6aWAKdVpja0017K27mxK8UsP2K*^v0&;Vj+X`pGK28|j38Z13{xe z$)Etx0AgrqplP56jT!(NG&mXqL8Cy)pa9SSVrXfgX`lv;8UPwJ5+V=~nK2Cn$)i)% zHkulSO{f^u2A-24qeDliG&55VQaDgI9!M0iy^I1FELaVK#h8o;)InNAL=Lj#mJHgH z8+n(_8e~CxC5VPvK>@IQ&o<%M#4qQru>Qrti_`b#~wzhyQqmSM=mw%;l9RZ)2<{AO74)X zIOZN$n+aT8rXjpsxo|O8m0~brI>9hMn&LWQ7fTnWR9fH!9fD~u1r^_}f~rj=(bbpq zr)~YU?eyy6K8*c&0FW$L3ri3TZQkl!)r~3zR@{=i1;0G*yA!!bw#NB=Jy4R9d&7z? z6NLR5=C%9#ejGEli??SKw7wOkajYCBie7ZT+*NL_3OfccMR|b>cpz***Z|r9X&_5z z+f9d+_?uxf3%Q{SnTR~C0=Wz&p*BJjK@3T;ACaTIf)dcd17H|20FN{M%^0%g097WC zlW8Qja_bkw&xk%JUO}(R9uaQ~qaFu``u6nPw9_(hAM!KIc*Se;1{$udtt@_n6FX@< zXT~%9YPj!T$DVYQ<6#+XSwX&}BNoX2S z))uy$%dKF&DJ`3!SZ&po(L)+li*C3kJV_ojXq|D?TpMp(Q9Oj_X^Kv4@?WMB0yCCT z6_o5UJ;rPEbe$S+ThvQ zxWLKTe$~fmTC~O^tzAS+R#6n`+%PB5pm*W47E3MQp)M5)F_bLbzYBvPpM9bd3}uv? zYqrBMpm>N`2t1y-g7{O4Oxk406tx;)baDXOCfFv5NItmjGDJ!hKS_czbHvdP? zG-FdR4Vo4v1agkJ`ygZ`qe3a9DK0p0XS5{+E3j2rQWca52stRCCJ2cp5dO$J{4NTN zspvgbIZ(Y-zyYKW+O(>@0}qaJ1o&TBG#il#5Hz_MLq@6-f2+sgwQ%;|siKtP%7>PU zCwOvddibh1C z7#1RO!cpu77i3X0Y7E`r!Z9#|VD{iu8~;8{p6GU&upii$LN5tH-TNUN7f5zHk**RS zst+8Eh-^(s2;*ZfT0HkJ8@8T?9bow2URTgaCFVNF8kQV*(Q>mNsL39Ig!b{FXc^N^ zF9{*h)uw_>y#|IBEBA{f^tS#2h3mTF0mj|$L1Y*2Q=zSeiIA2+Tco%zEx&X16T ztDNABORPWcYzhcxZ5*cy;lnR6;1_w~D9hFdFRC$Jb%jAle zbY4lMGzN4)u2xdgDbCbsE)yV>t9)5dryM9srNdW<@^ea9n1OQ@rXZgrq>7AYf&|Z& zd+t7O!50Pwmf8n!PDKb&0TnkItwV{8KkmZXdk7KD)N`a%&JhDZnC`O!-M1adORA1A%>yn`WC z8rr>HiUQVl6+yhAr7;tvW^O0}t^1}%Bwt;Q2Iw6+xu}X4(7#1*8nE!JVxCu*msGfD zf#P5stS(JxKkc9r#K}Qwa-pE%sfrkyW5BVJy$~n^NMMXn)unVCL}?6#>uCw#jR9BU zJXwXv6@yDI0PEF6C-1yu4u6C^!uCG0`!t5o(x9kM{}*yaI8cxuFOy#nduPW0 diff --git a/data/standardsMetadata.rda b/data/standardsMetadata.rda index 638a5e6690efcc5ef380dcfd8dda176c7ee7f97e..6c541a64320b3d95a5320ecd722edf91d9841c44 100644 GIT binary patch literal 731 zcmV<10wn!HT4*^jL0KkKS;2mlK>z}1f5HF%|NY=+umC@I-(bJ*-{3$11ONa5zyh+= zL0AG*(38+Xihil;7$5>+4G%&+0gzbreqeD$J0QCR>00003hK5ZvG|14?BOu7g(8vr;88iWtBM{OangA^n zvK2UyFGUq#@#d<3tN<%K=NS?y7&@YG5ZuQQ``6#F9>_RR}bYPDG)R1Kjv zgbjc&02&z4L=abE3#K6^(nOjP7mHmCK{8?!U?~d;kf{zq&BT@LmHUMvK>ge10@Z6Z z8Qf1nKa^NeGLsBtOc#fkdsm}iUH#qj6^&=EB(h>&Ln%Oc2LTp9h86_E5xPLv4c_Ot zy-?#G8AuF3y2DDPR83o~8X>v2ftw0Ku`ynA0Tw<*b6(=}<6{-|Gv^l6bvMTZ3iei- z+#%9y`;>>#6j&vyvmcCd5Cu!R1Pu`kpe``23WnAAi0%>BLrdt0yG){%qD=)8Z==V3`UrlF*F!PfshQCNTKfluu_Ew z7b1$Z86^glEY8B0M5S0v(XGN+V<0YeON!Yi8MsNboRpTbN~#=4`{6RU45SmHsb$TY zwiJy*RgTS;mFa4n0)Kp@^VCKL^SG7dKxn3YpOn3aPM7fY=CP5UVgy5XOdr zYY;?eLTw~SXh5?w!H6{zNe!VA1-{4;UD_*kqDNUIS5PYg{VMkXub+yVo&=T&J!s%n z#Z{Xs{qnhk3#?Qf&$LswBcCbc(-?~3>Ki2)f*4pT3lr%E@NiB%aPXIhF$0w{4=XEG z)1kw~*T2cUk1RP0FEE~b>Ct!Rf0dJT$Bc9e`TB>21pP@|@oyS=3D7eNl$>Cij70#- z(2$`3s$(+W8W5Mqj~nV~%UKZ%$g{fz-O%dzrJinutcu9OkRqxPLFO^g3i2Xy%ZeIz zk|3+Cl0rJwuIP}jbX2W#77ZfhGPo{qR|X;_MKe{!yZ~iY6xue=^;f*sVnmaq@&J@- z8*a4qxTreRl)Tlgo&uG3|E~)O0}}(LsKmVPO^|_q4Io4%u!=uLgLG|VJJX?+#gts)(Myj58wzh-DIk=L)gW=g2uBYf hVivmo8beHB6q{XP?%T|S$iof(F64@Ep&*JTC;p@7Il2G< From 4abffcb0e3379f4925a4b82c8816c2d5bf632867 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:53:49 -0400 Subject: [PATCH 26/98] helper function to get subset of chart-specific settings --- R/getChartSettings.R | 19 +++++++++++++++++++ man/getChartSettings.Rd | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 R/getChartSettings.R create mode 100644 man/getChartSettings.Rd diff --git a/R/getChartSettings.R b/R/getChartSettings.R new file mode 100644 index 00000000..58004adf --- /dev/null +++ b/R/getChartSettings.R @@ -0,0 +1,19 @@ +#' Get chart-specific settings +#' +#' Subset master settings list to chart-specific settings list only +#' +#' @param settings Settings list containing settings for all selected charts. +#' @param chart The chart for which settings should be returned. +#' +#' @return Chart-specific settings +#' +#' @examples +#' settings <- safetyGraphics::generateSettings(standard="ADaM") +#' safetyGraphics:::getChartSettings(settings = settings, chart = "edish") +#' +#' @keywords internal +#' +getChartSettings <- function(settings, chart){ + settings_names <- names(safetyGraphics::generateSettings("None",chart=chart)) + return(settings[settings_names]) +} diff --git a/man/getChartSettings.Rd b/man/getChartSettings.Rd new file mode 100644 index 00000000..190678e2 --- /dev/null +++ b/man/getChartSettings.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/getChartSettings.R +\name{getChartSettings} +\alias{getChartSettings} +\title{Get chart-specific settings} +\usage{ +getChartSettings(settings, chart) +} +\arguments{ +\item{settings}{Settings list containing settings for all selected charts.} + +\item{chart}{The chart for which settings should be returned.} +} +\value{ +Chart-specific settings +} +\description{ +Subset master settings list to chart-specific settings list only +} +\examples{ +settings <- safetyGraphics::generateSettings(standard="ADaM") +safetyGraphics:::getChartSettings(settings = settings, chart = "edish") + +} +\keyword{internal} From f793c959f63b2e1d1ea57075e2ae416b2d5f07b0 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:54:20 -0400 Subject: [PATCH 27/98] handle case where chart has no field settings --- R/validateSettings.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/validateSettings.R b/R/validateSettings.R index 59b982e5..2f369b26 100644 --- a/R/validateSettings.R +++ b/R/validateSettings.R @@ -69,7 +69,9 @@ validateSettings <- function(data, settings, chart="eDish"){ columnChecks <- dataKeys %>% purrr::map(checkColumn, settings=settings, data=data) #Check that non-null field/column combinations are found in the data + fieldChecks <- NULL allKeys <- getSettingsMetadata(charts=chart, filter_expr = .data$field_mapping, cols = c("text_key","setting_type")) + if (!is.null(allKeys)){ fieldKeys <- allKeys %>% filter(.data$setting_type!="vector")%>% pull(.data$text_key)%>%textKeysToList() #Add items in vectors to list individually @@ -85,6 +87,7 @@ validateSettings <- function(data, settings, chart="eDish"){ } } fieldChecks <- fieldKeys %>% purrr::map(checkField, settings=settings, data=data ) + } #Check that settings for mapping numeric data are associated with numeric columns numericKeys <- getSettingsMetadata(charts=chart, filter_expr=.data$column_type=="numeric", cols="text_key")%>%textKeysToList() From 6d23c6958a6a5cbe2758b4d8ee3ad7539c817cc6 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:57:04 -0400 Subject: [PATCH 28/98] update module per chart addition --- .../modules/renderSettings/renderSettings.R | 55 +++++++++++++------ .../modules/renderSettings/renderSettingsUI.R | 5 +- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 014d686c..dcac62dd 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -88,8 +88,11 @@ renderSettings <- function(input, output, session, data, settings, status){ observe({ - column_keys <- getSettingsMetadata(charts=input$charts, - filter_expr = field_mapping==TRUE) %>% + field_rows <- getSettingsMetadata(charts=input$charts, + filter_expr = field_mapping==TRUE) + + if(!is.null(field_rows)){ + column_keys <- field_rows %>% pull(field_column_key) %>% unique %>% as.list() @@ -127,6 +130,7 @@ renderSettings <- function(input, output, session, data, settings, status){ } ) }) + } }) @@ -157,7 +161,12 @@ renderSettings <- function(input, output, session, data, settings, status){ r_ratio_filter = input$r_ratio_filter, r_ratio_cut = input$r_ratio_cut, showTitle = input$showTitle, - warningText = input$warningText) + warningText = input$warningText, + unit_col = input$unit_col, + details = as.list(input$details), + filters = as.list(input$filters), + group_cols = input$group_cols #as.list(input$group_cols) + ) if (! is.null(input$`baseline--values`)){ if (! input$`baseline--values`[1]==""){ @@ -172,19 +181,19 @@ renderSettings <- function(input, output, session, data, settings, status){ values = input$`analysisFlag--values`) } } - - if (!is.null(input$filters)){ - for (i in 1:length(input$filters)){ - settings$filters[[i]] <- list(value_col = input$filters[[i]], - label = input$filters[[i]]) - } - } - if (!is.null(input$group_cols)){ - for (i in 1:length(input$group_cols)){ - settings$group_cols[[i]] <- list(value_col = input$group_cols[[i]], - label = input$group_cols[[i]]) - } - } + # + # if (!is.null(input$filters)){ + # for (i in 1:length(input$filters)){ + # settings$filters[[i]] <- list(value_col = input$filters[[i]], + # label = input$filters[[i]]) + # } + # } + # if (!is.null(input$group_cols)){ + # for (i in 1:length(input$group_cols)){ + # settings$group_cols[[i]] <- list(value_col = input$group_cols[[i]], + # label = input$group_cols[[i]]) + # } + # } return(settings) }) @@ -210,8 +219,14 @@ renderSettings <- function(input, output, session, data, settings, status){ } } - validateSettings(data(), settings_new, chart="eDish") + out <- list() + + charts <- isolate(input$charts) + for (chart in charts){ + out[[chart]] <- validateSettings(data(), settings_new, chart=chart) + } + return(out) }) @@ -221,7 +236,11 @@ renderSettings <- function(input, output, session, data, settings, status){ status_df <- reactive({ req(status_new()) - status_new()$checks %>% + #status_new()$checks %>% + flatten(status_new()) %>% + keep(., names(.)=="checks") %>% + bind_rows() %>% + unique %>% group_by(text_key) %>% mutate(num_fail = sum(valid==FALSE)) %>% mutate(icon = ifelse(num_fail==0, "",""))%>% diff --git a/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R b/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R index 83a3f4c3..b2ee8589 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R @@ -8,8 +8,9 @@ renderSettingsUI <- function(id){ checkboxGroupInput( ns("charts"), "Select Chart(s):", - choices = c("e-DISH" = "edish"), - selected="edish" + choices = c("e-DISH" = "edish", + "Safety Histogram" = "safetyhistogram"), + selected=c("edish", "safetyhistogram") ) ) ), From f6d92a1c439948dd091b0df1dd3131e634b5f7e3 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:57:22 -0400 Subject: [PATCH 29/98] safety histogram chart module --- .../render_safetyhistogram_chart.R | 19 +++++++++++++++++++ .../render_safetyhistogram_chartUI.R | 8 ++++++++ 2 files changed, 27 insertions(+) create mode 100644 inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R create mode 100644 inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R new file mode 100644 index 00000000..a507b477 --- /dev/null +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R @@ -0,0 +1,19 @@ +render_safetyhistogram_chart <- function(input, output, session, data, settings, valid){ + + ns <- session$ns + + + # render eDISH chart if settings pass validation + output$chart <- renderSafetyHistogram({ + req(data()) + req(settings()) + + if (valid()==TRUE){ + trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) + safetyHistogram(data = data(), settings = settings()) + } else{ + return() + } + }) + +} \ No newline at end of file diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R new file mode 100644 index 00000000..8be0d6f1 --- /dev/null +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R @@ -0,0 +1,8 @@ +render_safetyhistogram_chartUI <- function(id){ + + ns <- NS(id) + + # tagList( + safetyHistogramOutput(ns("chart")) + # ) +} \ No newline at end of file From b458b712dc118507949462db9787b1ccd0eef43f Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:57:58 -0400 Subject: [PATCH 30/98] temporarily remove condition due to bug --- .../modules/renderChart/render_edish_chart.R | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R index aaeb3602..b5a681e5 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -23,12 +23,13 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ req(data()) req(settings()) - if (valid()==TRUE){ + print(valid()) + # if (valid()==TRUE){ trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) eDISH(data = trimmed_data, settings = settings()) - } else{ - return() - } + # } else{ + # return() + # } }) @@ -54,7 +55,7 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ style="padding: 8px;", #then little tweak to ensure vertical alignment downloadButton(ns("reportDL"), "Export Chart")) ) ) - } + } else { removeUI(selector = paste0("#", ns("download"))) } From ed24047d5bd4d0f5a912944fa8402864941fecbb Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 14:58:21 -0400 Subject: [PATCH 31/98] dynamic chart UI --- inst/eDISH_app/server.R | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 7ee7612a..f000d9bb 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -35,7 +35,9 @@ function(input, output, session){ # update settings navbar output$settings_tab_title = renderUI({ - if (settings_new$status()$valid==TRUE){ + num_valid <- settings_new$status() %>% flatten() %>% keep(., names(.)=="valid") %>% unlist() %>% sum() + # if (settings_new$status()$valid==TRUE){ + if(num_valid >0) { HTML(paste("Settings", icon("check", class="ok"))) } else { HTML(paste("Settings", icon("times", class="notok"))) @@ -51,53 +53,51 @@ function(input, output, session){ # } # }) - # ## this currently wipes away everything anytime there's a change in chart selections + # ## this currently wipes away everything anytime there's a change in chart selections OR + # change in validation status observe({ charts <- settings_new$charts() # remove whole navMenu and all existing chart tabs removeTab(inputId="tabs", target="Charts") + appendTab(inputId = "tabs", navbarMenu("Charts")) # for each chart, append a new tab to the menu and place the module UI output lapply(charts, function(chart){ - tabfun <- match.fun(paste0("render_", chart, "_chartUI")) - tabid <- paste0(chart, "_tab_title") - tabcode <- tabPanel(title = htmlOutput(tabid), tabfun(paste0("chart", chart))) + + status <- settings_new$status()[[chart]]$valid + if(status==TRUE){ + tab_title <- HTML(paste(chart, icon("check", class="ok"))) + } else { + tab_title <- HTML(paste(chart, icon("times", class="notok"))) + } + + tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab + # tabid <- paste0(chart, "_tab_title") + tabcode <- tabPanel(title = tab_title, #htmlOutput(tabid), + tabfun(paste0("chart", chart))) appendTab(inputId = "tabs", - navbarMenu("Charts", tabcode)) + tabcode, + menuName = "Charts") }) }) - allcharts <- c("edish") # grab from metadata - all available charts - + + # call all chart modules + allcharts <- c("edish", "safetyhistogram") # grab from metadata - all available charts + for (chart in allcharts){ - - name <- paste0(chart,"_tab_title") - - output[[name]] = renderUI({ - status <- settings_new$status()$valid - if(status==TRUE){ - label <- HTML(paste(chart, icon("check", class="ok"))) - } else { - label <- HTML(paste(chart, icon("times", class="notok"))) - } - }) - - modfun <- match.fun(paste0("render_", chart, "_chart")) - callModule(modfun, paste0("chart", chart), + + modfun <- match.fun(paste0("render_", chart, "_chart")) + callModule(module = modfun, + id = paste0("chart", chart), data = reactive(dataUpload_out$data_selected()), settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()$valid)) + valid = reactive(settings_new$status()[[chart]]$valid)) ## why is this always sending SH } - - # # module to render eDish chart - # callModule(renderEDishChart, "chartEDish", - # data = reactive(dataUpload_out$data_selected()), - # settings = reactive(settings_new$settings()), - # valid = reactive(settings_new$status()$valid)) session$onSessionEnded(stopApp) From 46169019662b41591b05cc0a59de7c8bf19e5802 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 15:01:31 -0400 Subject: [PATCH 32/98] source module --- inst/eDISH_app/global.R | 2 ++ inst/eDISH_app/ui.R | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/inst/eDISH_app/global.R b/inst/eDISH_app/global.R index 88557c36..5cab0bd5 100644 --- a/inst/eDISH_app/global.R +++ b/inst/eDISH_app/global.R @@ -17,6 +17,8 @@ source('modules/renderSettings/renderSettings.R') source('modules/renderChart/render_edish_chartUI.R') source('modules/renderChart/render_edish_chart.R') +source('modules/renderChart/render_safetyhistogram_chartUI.R') +source('modules/renderChart/render_safetyhistogram_chart.R') source('modules/dataUpload/dataUploadUI.R') source('modules/dataUpload/dataUpload.R') diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index d87ef885..2f4ea5e0 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -13,7 +13,8 @@ tagList( fluidPage( renderSettingsUI("settingsUI") ) - ) + )#, + # navbarMenu("Charts") # tabPanel(title = htmlOutput("chart_tab_title"), # id = "charttab", # renderEDishChartUI("chartEDish") From 9b1d4789816613225eb2c9637c9defaf27fe2ad1 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 15:40:21 -0400 Subject: [PATCH 33/98] tweaks --- inst/eDISH_app/global.R | 3 +++ inst/eDISH_app/server.R | 2 -- inst/eDISH_app/ui.R | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/inst/eDISH_app/global.R b/inst/eDISH_app/global.R index 5cab0bd5..46111ac8 100644 --- a/inst/eDISH_app/global.R +++ b/inst/eDISH_app/global.R @@ -11,6 +11,9 @@ library(stringr) library(DT) library(haven) +# create vector of all possible charts +allcharts <- c("edish","safetyhistogram") + ## source modules source('modules/renderSettings/renderSettingsUI.R') source('modules/renderSettings/renderSettings.R') diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index f000d9bb..6b83a00e 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -86,8 +86,6 @@ function(input, output, session){ # call all chart modules - allcharts <- c("edish", "safetyhistogram") # grab from metadata - all available charts - for (chart in allcharts){ modfun <- match.fun(paste0("render_", chart, "_chart")) diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index 2f4ea5e0..ffb4f535 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -13,8 +13,8 @@ tagList( fluidPage( renderSettingsUI("settingsUI") ) - )#, - # navbarMenu("Charts") + ), + navbarMenu("Charts") # tabPanel(title = htmlOutput("chart_tab_title"), # id = "charttab", # renderEDishChartUI("chartEDish") From b8d5ceee2256f1f513e82507e6c54d7fa3b7ce25 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 16:02:16 -0400 Subject: [PATCH 34/98] set unit_col default to NULL --- data-raw/defaults.Rds | Bin 551 -> 541 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data-raw/defaults.Rds b/data-raw/defaults.Rds index ee90d6b78360aeeb81bb09dd3cbf7141cae48a07..fe83bd33492c82a217c9f6850316f9016029ec35 100644 GIT binary patch literal 541 zcmV+&0^ZV7Z4bV!IRS#cT=UL03y;qO0!VQ4S4p0N+yb#iHW1~Z9wBH7MMCKc?Tnfb(i!nR1 zktv3}1O>9S!hV!c{u}n<0dowU++}*}gjNTj9RXMSni`?lfs(1o44_lCFD5oGqPcf5 zo2_)S7Ebc$(|}Pzm1}+%eJ;GZ$JsgK4#a_v>snZg5nuP$MuIvy5%T9hM#w=%>mbv# zKQujxEIo=U4N}=VCOwoKbk!TDI8NTZCGiW7;-nwbetyU{>*jr`Umw7S7QCVBN?0ls zatyf>x15#+ww1^!!b&@ey%Y=_XG^+LEzMCJA_qwg&vjjM#h_~?4Z>kmE9yc*r?sTm z87~>#aGGmX2vehj4@A>edqcG;L5UxIymf%N45rxf&|~!48l7{756LbE#12U$k)xYc z#2geYwM8J3y1Q^zVr4di2}TIqu7bj%`(PchxniP+wFG0$A{z+tQ}qN f%xz@Gig-2L#W#tp!Ll%zI8^l)sr7O&*aQFow;BVS literal 551 zcmV+?0@(c@iwFP!000002BlL?kJB&^^(Jh0yX=;VB5~p1KhSdLfG7w~XoYA%+@fjh zrWTG}`D44`#6RLs@jEEvG;XtD_e3Sv@tb*L`_1#W3qnXj($j<_DSoDtW=+T##|!d^ z3~?R^mKR#KtKkhu&pW{5ii7p03r!-_kuv`kor_;VV z<6bCIo`V8eTVX#)DF3BByC)o}!`obMoY3k3)IH#Gf2XZb>>$a=8rZBwK@nEkQS7B);5eJnxvFV_<`4x)YIvcmiYo?9D`_weqgqiH3OcPN z#m;!i=#tYytCcVnCioyUt+h8)n-Y}x(ekYWtYxsohKC+w*2d_9Gki#P);s=Sk$rx@ z_|nSTm6b%*uIEv;>z8vxlMekM#0mt^Vo+002?i5`q8# From 9480b0673008f9644b64226763e8625259c4ffe4 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Thu, 21 Mar 2019 16:02:30 -0400 Subject: [PATCH 35/98] set unit_col default to NULL --- data/settingsMetadata.rda | Bin 2015 -> 2014 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/settingsMetadata.rda b/data/settingsMetadata.rda index 6b6c090e0d9e1c150446f1194cb192d25d67a74f..bf253d4996896668e113f10a843d177c7caa499b 100644 GIT binary patch delta 2000 zcmV;>2QT>F58e+ELRx4!F+o`-Q(0>rVpjkK@sSZGe_mO8-f8QF?kj9)&;X-AZH9$3 zo~h}RNv4LFnHm`y27nD302vrTriZBY0NS3Q(nUWZsML87WYOwqVGS}GX^1o#Fh&Gm zBO^>e83fX36+cmmZ4*EM4Ke^48Z-mc27m^D0Fe?QPil=kQ_++?L6bv3G6PRUX`lcD zKmZLie>h?^(?OsO0000D20^BQpaVvLF%1BTBxoswo{6D^BPJ>2H9V$`JtIIpK=lLC zXwVt}c})c<;Z@-?pwZ=%QA^`(PD(LLB-u!n0fbRKGzZTX#Ot6E zn1gFF+^aRAD#nWGP05$*btOZ=2u;bZt7O!Se~^HeJ_wHwqQAA#2UtCG+ve4xNg)`T zO^Dhq;)-SO(b~FPw+Ia1tDX24tQ(;^q#pS)duLNhKr2H67LemaP={81J*Fb7s?!?D z>a0349j*7xoT^u3@4qI**zBasj36bV2?AgUT{dhq ze{`Px7<@F1pp_9mdSG&uMA@^TEhTytbF)b~#FMi!&3=k4Hy;_73>XYOQV&rxua z7y{-96EGO&pvlFK$e1$I%-J%F4YMy(D?u@_GEAJ647o^RC~(%Yw1P6yr8e{iEvUB? zrAlQ=OGjkDpGuL#ehVRFvZ@eCe}M*cR2@Cn;A>b-C!S~%CMXlU@jBRM1rDMicqmu$ z?QGr@E35_x854r!)&Nt2^yp!R#o28cff?F)?$rmhVd^QWDqGE+IwQ}&qV#jpTHn}H z3NKnV&8;)D*xBy0f;I+eW|9|Ksy~9R;1$jmuvn5W2k^WxVNoe6cvZxae@W=N(J6Wc zWh_cu2mAV3AU{lD6p#rQ71WC5iO_v)Qp2yzY&c>V&UD;zWC;_R=5FPDf9@GP<@W4k z{GTxsTwREqHmYi9S7n&%?}Sb~Gkw20cyF-t9qi5JSFHH1_)oD(Q7t^hxTOJ12@h@- zmUB(Gyie9+bnd;#nZMRbG=K z7t6+JgMeJ4M@-96R2bHnI-R2uu-@A8FpwbMYFz)&Le>`#W_!T4f9vfI%q-~h1``g% z!y^LW(aV>7Ao8uv0JLKQ%OXN-Y$S|1G7#?jlI|!VD_KS(j0GoQI56vjvStt~Q%X29 zuWmSMT{e1lNO^<2ZGujO$UBx3!Rn;#Jwe@>k0k>>nl!T@vJ7R2Jg|;#!_hK(^8Fjr zwv&Yb{1E9ZtL8g|e+^3v8>$9#IJbU9h}jrUhWRDDO)QizWKtV`_WT8gVs3$RABgA- zm*|pu3{4*vZ6vO4%-mfJ`z5C8k>oB_7NDx4s4so24-jS!ReyB$5(Hj7UN;gCByYiHJ1&h9Y(h4bhB7+nQ+EErKTr z=i-xAf3wGuQzik`zn=lY&jUTU=0@x^##XA6i8FID#JL?~W>6-9+JTcDr?T{`sl*gt zOnX%Vkm%nQpumLG$k^zVIl{nh4O59bcWbn9mfRdm?SKT)Hik34clIHYFARfxGeqw3 z;EeQ6D=*cD-#VG8LXyXTfMC*DwrYU{g}j+-HUgRM27=lsKrK?HYzB!qVi?{y@D-h4 iG}T6npixmb>}H62C+&h1qRmu4_`8xR!i0lZ>k_y!AA5ZO delta 2001 zcmV;?2QK*D58n?FLRx4!F+o`-Q(3Luh5P^o_>mDNe+l(~I6DZxs8SwIzri9Jc` zJtWgchKz=q4H{@@XaE7~Hl~;$XnKtR+Mc1q5vH0A0B8UJ0BAA|Gz|b6Gy#Zc4j7Fz z&}aid0009)kZGW30MVcfLqH@%f~TgQq|?;enH$wMlSimD4FCYp0000007Vfs2{T5J z^$j!)e+?QBPyiYL02%-TKr{&w0tf`q(3+V~$uemknW|{@Jf?v4Jx8d}X}wKPP#FMi zOazEF#7Bw`asl%(8(5SLAZ$tMATT(aZxJH2mL}*Zi6Q3*L^LKegnFP4p{B&^pc0sa z)tzjtJt#`CqlINvf2Lod)|C$IAsUgENl6(Ee>TPl^w>*xwmzzf0>z7r_kQ0*&;myW zlVUxFTq1ubu5Q^FUJx9AOC`<*%+7+>p*=K{_S*8L0wq}pj4LyNf+rH6bGAsni}W3j z=){|oUA^D!DX4hmZ8~@UTT>2PD=KN7bh>pAt185|iYla0$t|(rq|1#231vb+m;x6u zf152DXA%l=p$b@tXh&6o3j>D&)nFN|pomC*Tm2amI zHjvnaX=rVd>z#-o$jtXu>95HhLgfp_UBkWny83!Hq%sWiKF^!P-aS!@(i)Aqz0U2Lvc)N zH(t|PtGZRQt*gG=%&NuVV)M}_>>Wx8$_<^JGeK6CB{DDGvA=8@3N=vC zA&>S46`)j^l;qyIMjte{@|i5`k}GRanBbWzR#FVfYB0eZ*0Qppe6>@emeCSh zXo|9S3=EnS4ja%|3niBDP?Z9Pe}fb%_g;cFxSCHs#sY|9c}R72nr;LYNQJGDYnTdJV0LvomLoiY3Y%Htb;bY9~h97pSeVXLLlVL66{bO6-q*xk~BQJ&#q^}H)(PDs4=?2nMVoiysA&ZHPU1oXRms_ts zq;#R}fEU_lK`AA$e`*jlEHPl9ZW*hY@i3#TFr96>O61bMhR))Udi(mOB+H1>!lgbl zoGh2Mx92Eeyf;vX81!AmXf4qt-3_coWI-?y5JUxa=B69~=+K=+2NoV1U}9YwpP+~w z5?&7qJp$$wjb0~;OADGBnGAr2S7>|N2RKyY!0wvPBySq`f7(znh;2AyL=bFzyCrJ$ zcnDc9G|`xCbA+ftEi*CSB%DnVsJNJ|m`g9F|b$5lj(3Jb9e z^DmYtt@j^U)weOG4O!4Zj&JB7TuaE*vpZU z5ny)M&~s)GnpPA7@rnS~5e(1X&BO&V5#Ja>Ge>7vgKY>Y9x`MugL5~J19~JQ$`DdV zB#Ox6Hk3z5&T><_Ny$LQ+ikJ7(3%*=nqqFo6-fYPf4;(?MHZ$xMvzSrPGH|$l4B{9 zBv(0alD!RNsc{9;=aN`VE0}0BdYbt&$z2!`xra8ka=9`fj!8*X6I7h0=7=IghMOeR z8X3|tQP#G?>pj+(vlDxvDH7ym%5r(VFEGqhM%J!v8^II>3%pQAAu~%&G%0CNpv}r? zM%USse|V_9H#kkqJDRzsDXYxBid<@`m{G@mzmZ4FsT9R-Gz&J3nYeDta5=#>x zg1A$JFoYcXnoz{Sj}pdf1VFCI0i-cTNa?DM2C`a1B)!Z4cq2ej^__X3vJ%^1)XSkA z-E%^Ica)Ojvt+%TiWHVSL<0tr$A_K-5*FXlH>?V891R8B3^Roa(hdo0+;L971xS?Q jq^%Hen Date: Fri, 22 Mar 2019 13:01:46 -0400 Subject: [PATCH 36/98] generalize to multiple charts --- inst/eDISH_app/modules/dataUpload/dataUpload.R | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/inst/eDISH_app/modules/dataUpload/dataUpload.R b/inst/eDISH_app/modules/dataUpload/dataUpload.R index 82ee0633..ee42b0d1 100644 --- a/inst/eDISH_app/modules/dataUpload/dataUpload.R +++ b/inst/eDISH_app/modules/dataUpload/dataUpload.R @@ -164,13 +164,13 @@ dataUpload <- function(input, output, session){ select(text_key) %>% pull() - generateSettings(standard=current_standard, chart="eDish", partial=partial, partial_keys = partial_keys) + generateSettings(standard=current_standard, partial=partial, partial_keys = partial_keys) } else { - generateSettings(standard=current_standard, chart="eDish") + generateSettings(standard=current_standard) } } else { - generateSettings(standard=current_standard, chart="eDish") + generateSettings(standard=current_standard) } }) @@ -180,8 +180,7 @@ dataUpload <- function(input, output, session){ req(data_selected()) req(settings()) validateSettings(data_selected(), - settings(), - chart="eDish") + settings()) }) exportTestValues(status = { status() }) From 493dfe5bb0d1bc6203e738539a55edc8dafc7634 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 22 Mar 2019 13:31:05 -0400 Subject: [PATCH 37/98] comments - other option that doesn't want to work in the loop.. --- inst/eDISH_app/server.R | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 6b83a00e..8e7d7e26 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -74,8 +74,9 @@ function(input, output, session){ } tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab - # tabid <- paste0(chart, "_tab_title") - tabcode <- tabPanel(title = tab_title, #htmlOutput(tabid), + #tabid <- paste0(chart, "_tab_title") + tabcode <- tabPanel(title = tab_title, + # title = htmlOutput(tabid), tabfun(paste0("chart", chart))) appendTab(inputId = "tabs", @@ -87,7 +88,17 @@ function(input, output, session){ # call all chart modules for (chart in allcharts){ - + + # tabid <- paste0(chart, "_tab_title") + # output[[tabid]] = renderUI({ + # status <- settings_new$status()[[chart]]$valid + # if(status==TRUE){ + # HTML(paste(chart, icon("check", class="ok"))) + # } else { + # HTML(paste(chart, icon("times", class="notok"))) + # } + # }) + modfun <- match.fun(paste0("render_", chart, "_chart")) callModule(module = modfun, id = paste0("chart", chart), From ba6cae3d8e307bc83e3be976bebfd8c0cfeec17a Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 14:51:20 -0400 Subject: [PATCH 38/98] "eDISH" -> "Histogram" --- R/safetyHistogram.R | 4 ++-- man/safetyHistogram.Rd | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/safetyHistogram.R b/R/safetyHistogram.R index 03b383b8..f76698ce 100644 --- a/R/safetyHistogram.R +++ b/R/safetyHistogram.R @@ -19,7 +19,7 @@ #' @examples #' \dontrun{ #' -#' ## Create eDISH figure customized to user data +#' ## Create Histogram figure customized to user data #'safetyHistogram(data=adlbc, #' id_col = "USUBJID", #' value_col = "AVAL", @@ -28,7 +28,7 @@ #' normal_col_high = "A1HI", #' unit_col = "PARAMCD") #' -#' ## Create eDISH figure using a premade settings list +#' ## Create Histogram figure using a premade settings list #' details_list <- list( #' list(value_col = "TRTP", label = "Treatment"), #' list(value_col = "SEX", label = "Sex"), diff --git a/man/safetyHistogram.Rd b/man/safetyHistogram.Rd index e97035f6..d76681dd 100644 --- a/man/safetyHistogram.Rd +++ b/man/safetyHistogram.Rd @@ -43,7 +43,7 @@ This function creates a Safety Histogram using R htmlwidgets. \examples{ \dontrun{ -## Create eDISH figure customized to user data +## Create Histogram figure customized to user data safetyHistogram(data=adlbc, id_col = "USUBJID", value_col = "AVAL", @@ -52,7 +52,7 @@ safetyHistogram(data=adlbc, normal_col_high = "A1HI", unit_col = "PARAMCD") -## Create eDISH figure using a premade settings list +## Create Histogram figure using a premade settings list details_list <- list( list(value_col = "TRTP", label = "Treatment"), list(value_col = "SEX", label = "Sex"), From 94e318ee16de2b46dbffe667b9f0581c415c1a77 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 14:51:56 -0400 Subject: [PATCH 39/98] add start_value --- data-raw/csv_to_rda.R | 2 +- data-raw/defaults.Rds | Bin 541 -> 548 bytes data-raw/generateDefaults.R | 1 + data-raw/settingsMetadata.csv | 1 + data-raw/standardsMetadata.csv | 1 + data/settingsMetadata.rda | Bin 2014 -> 2060 bytes data/standardsMetadata.rda | Bin 731 -> 739 bytes 7 files changed, 4 insertions(+), 1 deletion(-) diff --git a/data-raw/csv_to_rda.R b/data-raw/csv_to_rda.R index cd77239c..3f97d5ee 100644 --- a/data-raw/csv_to_rda.R +++ b/data-raw/csv_to_rda.R @@ -7,7 +7,7 @@ usethis::use_data(adlbc, overwrite = TRUE) partialSettingsMetadata <- read.csv("data-raw/settingsMetadata.csv", stringsAsFactors = FALSE) #merge defaults on to settingsMetadata -defaults <- readRDS("data-raw/defaults.Rds") #why is this not working... grrrr +defaults <- readRDS("data-raw/defaults.Rds") settingsMetadata <- dplyr::full_join(partialSettingsMetadata, defaults, by="text_key") diff --git a/data-raw/defaults.Rds b/data-raw/defaults.Rds index fe83bd33492c82a217c9f6850316f9016029ec35..8987b444b58a87a28c917772243f22ac3ae30ff8 100644 GIT binary patch delta 522 zcmV+l0`>i!1f&Fz6n`+CN2w?hD&4ig1ksRCSKJ0t$@wW>?NJ7%ngd{00(|NlmWQOAvc|s;Qp9z-bTK2oiElAG? zz-7(BdNYLP5o&$yVzK(Pe$@0Onx2nM>-Uf6``nwRN}C#FI6gd+de@`R252S9s)w(v z^Q>Xe+^a`e;Re7(3#b9|v=Gv9W1~Z9wBH7MMCKc?Tnfb(i!nR1ktv3}1O>9OkggPl9!d^U>y1+! zC-2^p_=R_J(vN9BKV+M=bC>GZ2k@bPD{tt!5|#>uv_tO1EvKb{Z6$Jwu+okqZ-Rm2 zY)MzDp*iYBTmU zDDk6@$1Y(mgDJK=v>m;+M(3R2+p^0MxkDmJ>}Tkiw_0J)n5BLDyZ delta 515 zcmV+e0{s1?1f2ws6n~?`OsOam8y5b7mOTqZL1KkQNK}YjbQ?Q~g=1HKOeU=OBYukS zpj@YMn+Y>3B009tec1Qh<8POQkc6bC2}x31rt@x3$PC9T@`OxqJ`*g&+0FN2v3)i^b~G`cc!DXnH<2t=~VM?{jaODs5_z;Xd$8>ZV7Z4bV!IRS#cT z=UL03y;qO0!VQ4S4p0N+yb#iHW1~Z9wBH7MMCKc?Tnfb(i!nR1ktv3}1O>9SksnZg5nuP$MuIvy5%T9hM#w=%>mbv#KQujxEIo=U4N}=VCOwoKbk!TDI8NTZCGiW7 z;-nwbetyU{>*jr`Umw7S7QCVBN?0m?6mkr?6Stg}2DX*RDZ)xSioFyJ9A`_qQZ3C< z93lrv4bOF5bH$)*B@M!1R4eL2LZ`K)*cmSw-Ef*~RR~j~gAYX0R(nIWDM5)JeY|ym zxeTV*^3Y@S+8Uj6h7ZXu2gD9ZB$1<=RpjXA^$O8sQ0u9Jx@+r@)eDF6$iDMB_9%%0 zymX`3iP6n6#$Si{pZMGDcE87m_=f-h diff --git a/data-raw/generateDefaults.R b/data-raw/generateDefaults.R index 0e29690e..195423f0 100644 --- a/data-raw/generateDefaults.R +++ b/data-raw/generateDefaults.R @@ -25,6 +25,7 @@ defaults <- tribble(~text_key, ~default, "showTitle", TRUE, "warningText", "Caution: This interactive graphic is not validated. Any clinical recommendations based on this tool should be confirmed using your organizations standard operating procedures.", "unit_col", NULL, + "start_value", NULL, "details", NULL, "missingValues", c("","NA","N/A") ) diff --git a/data-raw/settingsMetadata.csv b/data-raw/settingsMetadata.csv index 90ed7429..4d1d7f86 100644 --- a/data-raw/settingsMetadata.csv +++ b/data-raw/settingsMetadata.csv @@ -25,5 +25,6 @@ TRUE,FALSE,r_ratio_cut,Default R Ratio Cut,Default cut point for R Ratio filter. TRUE,FALSE,showTitle,Show Chart Title? ,Specifies whether the title should be drawn above the controls.,logical,FALSE,FALSE,NA,FALSE,,appearance TRUE,FALSE,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 FALSE,TRUE,unit_col,Unit column,Unit of measure variable name,character,TRUE,TRUE,character,FALSE,,data +FALSE,TRUE,start_value,Measure start value,Value of variable defined in measure_col to be rendered in the histogram when the widget loads,character,FALSE,FALSE,NA,TRUE,measure_col,data FALSE,TRUE,details,Details columns,"An optional list of specifications for details listing. Each column to be added to details listing is a nested, named list (containing the variable name: ""value_col"" and associated label: ""label"") within the larger list.",vector,FALSE,TRUE,NA,FALSE,,data FALSE,TRUE,missingValues,Missing values,Values defining a missing value in the selected 'value' column,vector,FALSE,FALSE,NA,FALSE,,data diff --git a/data-raw/standardsMetadata.csv b/data-raw/standardsMetadata.csv index 670fe145..bd868f36 100644 --- a/data-raw/standardsMetadata.csv +++ b/data-raw/standardsMetadata.csv @@ -25,5 +25,6 @@ r_ratio_cut,, showTitle,, warningText,, unit_col,STRESU,PARAMCD +start_value,, details,, missingValues,, diff --git a/data/settingsMetadata.rda b/data/settingsMetadata.rda index bf253d4996896668e113f10a843d177c7caa499b..27f1bf73b0423c543f924dab7fd1f2b05773ce33 100644 GIT binary patch literal 2060 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 VJzfZRqKYs0yOJrwgo92p+7MO8KXU*8 literal 731 zcmV<10wn!HT4*^jL0KkKS;2mlK>z}1f5HF%|NY=+umC@I-(bJ*-{3$11ONa5zyh+= zL0AG*(38+Xihil;7$5>+4G%&+0gzbreqeD$J0QCR>00003hK5ZvG|14?BOu7g(8vr;88iWtBM{OangA^n zvK2UyFGUq#@#d<3tN<%K=NS?y7&@YG5ZuQQ``6#F9>_RR}bYPDG)R1Kjv zgbjc&02&z4L=abE3#K6^(nOjP7mHmCK{8?!U?~d;kf{zq&BT@LmHUMvK>ge10@Z6Z z8Qf1nKa^NeGLsBtOc#fkdsm}iUH#qj6^&=EB(h>&Ln%Oc2LTp9h86_E5xPLv4c_Ot zy-?#G8AuF3y2DDPR83o~8X>v2ftw0Ku`ynA0Tw<*b6(=}<6{-|Gv^l6bvMTZ3iei- z+#%9y`;>>#6j&vyvmcCd5Cu!R1Pu`kpe``23WnAAi Date: Tue, 26 Mar 2019 14:52:43 -0400 Subject: [PATCH 40/98] remove commments --- .../modules/renderChart/render_edish_chart.R | 9 -------- .../renderChart/render_edish_chartUI.R | 6 +----- .../render_safetyhistogram_chartUI.R | 4 +--- .../modules/renderSettings/renderSettings.R | 14 ------------- inst/eDISH_app/server.R | 21 +------------------ inst/eDISH_app/ui.R | 4 ---- 6 files changed, 3 insertions(+), 55 deletions(-) diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R index b5a681e5..abaf4659 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -23,7 +23,6 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ req(data()) req(settings()) - print(valid()) # if (valid()==TRUE){ trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) eDISH(data = trimmed_data, settings = settings()) @@ -33,14 +32,6 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ }) - # output$chart_title = renderUI({ - # if (valid()==TRUE){ - # HTML(paste("eDISH", icon("check", class="ok"))) - # } else { - # HTML(paste("eDISH", icon("times", class="notok"))) - # } - # }) - # insert export chart button if settings pass validation # remove button if validation fails observeEvent(valid(), { diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chartUI.R b/inst/eDISH_app/modules/renderChart/render_edish_chartUI.R index dbad1f22..4f4885db 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chartUI.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chartUI.R @@ -10,9 +10,5 @@ render_edish_chartUI <- function(id){ ns <- NS(id) - # #tagList( - # tabPanel("edish", - eDISHOutput(ns("chart")) #) - #) - + eDISHOutput(ns("chart")) } \ No newline at end of file diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R index 8be0d6f1..7c524097 100644 --- a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R @@ -2,7 +2,5 @@ render_safetyhistogram_chartUI <- function(id){ ns <- NS(id) - # tagList( - safetyHistogramOutput(ns("chart")) - # ) + safetyHistogramOutput(ns("chart")) } \ No newline at end of file diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index dcac62dd..8a77b7c7 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -52,7 +52,6 @@ renderSettings <- function(input, output, session, data, settings, status){ #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 @@ -181,19 +180,6 @@ renderSettings <- function(input, output, session, data, settings, status){ values = input$`analysisFlag--values`) } } - # - # if (!is.null(input$filters)){ - # for (i in 1:length(input$filters)){ - # settings$filters[[i]] <- list(value_col = input$filters[[i]], - # label = input$filters[[i]]) - # } - # } - # if (!is.null(input$group_cols)){ - # for (i in 1:length(input$group_cols)){ - # settings$group_cols[[i]] <- list(value_col = input$group_cols[[i]], - # label = input$group_cols[[i]]) - # } - # } return(settings) }) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 8e7d7e26..8f53f169 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -1,7 +1,7 @@ # Server code for safetyGraphics App # - calls dataUpload module (data tab) # - calls renderSettings module (settings tab) -# - calls renderEDishChart (chart tab) +# - calls chart modules (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 @@ -36,7 +36,6 @@ function(input, output, session){ # update settings navbar output$settings_tab_title = renderUI({ num_valid <- settings_new$status() %>% flatten() %>% keep(., names(.)=="valid") %>% unlist() %>% sum() - # if (settings_new$status()$valid==TRUE){ if(num_valid >0) { HTML(paste("Settings", icon("check", class="ok"))) } else { @@ -44,14 +43,6 @@ function(input, output, session){ } }) - # update charts navbar - # output$chart_tab_title = renderUI({ - # if (settings_new$status()$valid==TRUE){ - # HTML(paste("Chart", icon("check", class="ok"))) - # } else { - # HTML(paste("Chart", icon("times", class="notok"))) - # } - # }) # ## this currently wipes away everything anytime there's a change in chart selections OR # change in validation status @@ -89,16 +80,6 @@ function(input, output, session){ # call all chart modules for (chart in allcharts){ - # tabid <- paste0(chart, "_tab_title") - # output[[tabid]] = renderUI({ - # status <- settings_new$status()[[chart]]$valid - # if(status==TRUE){ - # HTML(paste(chart, icon("check", class="ok"))) - # } else { - # HTML(paste(chart, icon("times", class="notok"))) - # } - # }) - modfun <- match.fun(paste0("render_", chart, "_chart")) callModule(module = modfun, id = paste0("chart", chart), diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index ffb4f535..5aaac5da 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -15,9 +15,5 @@ tagList( ) ), navbarMenu("Charts") - # tabPanel(title = htmlOutput("chart_tab_title"), - # id = "charttab", - # renderEDishChartUI("chartEDish") - # ) ) ) From 4ea34ba3b5b782743a719f02af4c04dd866a0bf7 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 14:53:35 -0400 Subject: [PATCH 41/98] alphabetize value options --- inst/eDISH_app/modules/renderSettings/util/createControl.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/eDISH_app/modules/renderSettings/util/createControl.R b/inst/eDISH_app/modules/renderSettings/util/createControl.R index 11b418a2..5466187a 100644 --- a/inst/eDISH_app/modules/renderSettings/util/createControl.R +++ b/inst/eDISH_app/modules/renderSettings/util/createControl.R @@ -56,7 +56,7 @@ createControl <- function(key, metadata, data, settings, ns){ 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 + choices <- unique(c(setting_value, sort(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 From bbd9419ee3b9d39b0a23338e7b3da3dfa7773abb Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 15:09:11 -0400 Subject: [PATCH 42/98] make sure numeric keys exist before performing checks --- R/validateSettings.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/R/validateSettings.R b/R/validateSettings.R index 2f369b26..d7724207 100644 --- a/R/validateSettings.R +++ b/R/validateSettings.R @@ -90,9 +90,12 @@ validateSettings <- function(data, settings, chart="eDish"){ } #Check that settings for mapping numeric data are associated with numeric columns + numericChecks <- NULL numericKeys <- getSettingsMetadata(charts=chart, filter_expr=.data$column_type=="numeric", cols="text_key")%>%textKeysToList() - numericChecks <- numericKeys %>% purrr::map(checkNumeric, settings=settings, data=data ) - + if (!is.null(numericKeys)){ + numericChecks <- numericKeys %>% purrr::map(checkNumeric, settings=settings, data=data ) + } + #Combine different check types in to a master list settingStatus$checks <-c(requiredChecks, columnChecks, fieldChecks, numericChecks) %>% { tibble( From 351df944d255de47d55a3ac89858f43aea7144c8 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 15:13:51 -0400 Subject: [PATCH 43/98] make sure numeric keys exist before numeric checks --- R/validateSettings.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/validateSettings.R b/R/validateSettings.R index d7724207..bc54f647 100644 --- a/R/validateSettings.R +++ b/R/validateSettings.R @@ -91,9 +91,9 @@ validateSettings <- function(data, settings, chart="eDish"){ #Check that settings for mapping numeric data are associated with numeric columns numericChecks <- NULL - numericKeys <- getSettingsMetadata(charts=chart, filter_expr=.data$column_type=="numeric", cols="text_key")%>%textKeysToList() + numericKeys <- getSettingsMetadata(charts=chart, filter_expr=.data$column_type=="numeric", cols="text_key") if (!is.null(numericKeys)){ - numericChecks <- numericKeys %>% purrr::map(checkNumeric, settings=settings, data=data ) + numericChecks <- numericKeys %>%textKeysToList() %>% purrr::map(checkNumeric, settings=settings, data=data ) } #Combine different check types in to a master list From 040ac6fb8a757f9ba1d1f1c6e15c7857107089af Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 15:27:59 -0400 Subject: [PATCH 44/98] add start_value --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 8a77b7c7..bccfb37b 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -162,6 +162,7 @@ renderSettings <- function(input, output, session, data, settings, status){ showTitle = input$showTitle, warningText = input$warningText, unit_col = input$unit_col, + start_value = input$start_value, details = as.list(input$details), filters = as.list(input$filters), group_cols = input$group_cols #as.list(input$group_cols) From be14f81f2aca6d24783d23148e0e95d53add3699 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 16:02:04 -0400 Subject: [PATCH 45/98] comment re: bug --- inst/eDISH_app/server.R | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 8f53f169..f37c7b7f 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -81,6 +81,10 @@ function(input, output, session){ for (chart in allcharts){ modfun <- match.fun(paste0("render_", chart, "_chart")) + + # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and + # the value for "valid" doesn't always get passed directly. + # this should be fixed by us moving to renderChart module callModule(module = modfun, id = paste0("chart", chart), data = reactive(dataUpload_out$data_selected()), From 151a7675fddd0d8f1af015eb1a9402aa5fcb3a72 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 16:02:18 -0400 Subject: [PATCH 46/98] comment out checks for validity b/c not working correctly --- inst/eDISH_app/modules/renderChart/render_edish_chart.R | 6 +++--- .../modules/renderChart/render_safetyhistogram_chart.R | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R index abaf4659..c7da2866 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -26,9 +26,9 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ # if (valid()==TRUE){ trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) eDISH(data = trimmed_data, settings = settings()) - # } else{ - # return() - # } + # } else{ + # return() + # } }) diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R index a507b477..3e87e3f4 100644 --- a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R @@ -8,12 +8,12 @@ render_safetyhistogram_chart <- function(input, output, session, data, settings, req(data()) req(settings()) - if (valid()==TRUE){ + # if (valid()==TRUE){ trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) safetyHistogram(data = data(), settings = settings()) - } else{ - return() - } + # } else{ + # return() + # } }) } \ No newline at end of file From 9077cd65a514f8ed8afaa73d6a5fafbaa970741c Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 16:02:32 -0400 Subject: [PATCH 47/98] add comment --- inst/eDISH_app/server.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index f37c7b7f..b95cbb33 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -84,7 +84,7 @@ function(input, output, session){ # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and # the value for "valid" doesn't always get passed directly. - # this should be fixed by us moving to renderChart module + # Moving to renderChart module will hopefully help here callModule(module = modfun, id = paste0("chart", chart), data = reactive(dataUpload_out$data_selected()), From d4a67b53eafca5bb6e93bb2da8303f45a5d2c840 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 26 Mar 2019 16:07:12 -0400 Subject: [PATCH 48/98] docs for safetyHistogram chart module --- .../renderChart/render_safetyhistogram_chart.R | 14 ++++++++++++++ .../renderChart/render_safetyhistogram_chartUI.R | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R index 3e87e3f4..24685959 100644 --- a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R @@ -1,3 +1,17 @@ +#' Render Safety Histogram chart - server code +#' +#' This module creates the Chart tab for the Shiny app, which contains the interactive Histogram graphic. +#' +#' Workflow: +#' (1) A change in `data`, `settings`, or `valid` invalidates the safety histogram chart output +#' (2) Upon a change in `valid`, the export chart functionality is conditionally made available or unavailable to user +#' +#' @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 + render_safetyhistogram_chart <- function(input, output, session, data, settings, valid){ ns <- session$ns diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R index 7c524097..09afeea5 100644 --- a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chartUI.R @@ -1,3 +1,11 @@ +#' Render safety hisotgram chart - UI code +#' +#' This module creates the Chart tab for the Shiny app, which contains the interactive safety histogram graphic. + +#' @param id The module-specific ID that will get pre-pended to all element IDs +#' +#' @return The UI for the Chart tab +#' render_safetyhistogram_chartUI <- function(id){ ns <- NS(id) From 9866c5edbb3da52865e3934e262c2d4c326810c2 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Wed, 27 Mar 2019 10:26:34 -0400 Subject: [PATCH 49/98] comment --- inst/eDISH_app/server.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index b95cbb33..62d3d418 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -89,7 +89,7 @@ function(input, output, session){ id = paste0("chart", chart), data = reactive(dataUpload_out$data_selected()), settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()[[chart]]$valid)) ## why is this always sending SH + valid = reactive(settings_new$status()[[chart]]$valid)) ## bad } From 2e76197ad63e3de79b28770b47155304585f7a34 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Wed, 27 Mar 2019 10:39:43 -0400 Subject: [PATCH 50/98] add chart_safetyhistogram to data doc --- R/settingsMetadata.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/settingsMetadata.R b/R/settingsMetadata.R index e2ba463e..e4b20340 100644 --- a/R/settingsMetadata.R +++ b/R/settingsMetadata.R @@ -5,6 +5,7 @@ #' @format A data frame with 25 rows and 10 columns #' \describe{ #' \item{chart_edish}{Flag indicating if the settings apply to the eDish Chart} +#' \item{chart_safetyhistogram}{Flag indicating if the settings apply to the Safety Histogram Chart} #' \item{text_key}{Text key indicating the setting name. \code{'--'} delimiter indicates a nested setting} #' \item{label}{Label} #' \item{description}{Description} From c26ca231766f8d0f79317e94f9d346af2d921baf Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Wed, 27 Mar 2019 13:09:28 -0400 Subject: [PATCH 51/98] remove status from Settings nav bar --- inst/eDISH_app/server.R | 10 ---------- inst/eDISH_app/ui.R | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 62d3d418..9e8dfdbc 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -33,16 +33,6 @@ function(input, output, session){ status = reactive(dataUpload_out$status())) - # update settings navbar - output$settings_tab_title = renderUI({ - num_valid <- settings_new$status() %>% flatten() %>% keep(., names(.)=="valid") %>% unlist() %>% sum() - if(num_valid >0) { - HTML(paste("Settings", icon("check", class="ok"))) - } else { - HTML(paste("Settings", icon("times", class="notok"))) - } - }) - # ## this currently wipes away everything anytime there's a change in chart selections OR # change in validation status diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index 5aaac5da..91f4746f 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -9,7 +9,7 @@ tagList( tabPanel(title = htmlOutput("data_tab_title"), dataUploadUI("datatab") ), - tabPanel(title = htmlOutput("settings_tab_title"), + tabPanel(title = "Settings", fluidPage( renderSettingsUI("settingsUI") ) From 291dde27298d9701735a49866f058fcbd9690619 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Thu, 28 Mar 2019 17:33:21 -0400 Subject: [PATCH 52/98] correcting some of the testthat tests --- man/checkColumn.Rd | 14 +++++++------- man/checkNumeric.Rd | 4 ++-- man/generateSettings.Rd | 6 +++--- man/settingsMetadata.Rd | 1 + tests/testthat/test_detectStandard.R | 6 +++--- tests/testthat/test_evaluateStandard.R | 10 +++++----- tests/testthat/test_generateSettings.R | 2 +- tests/testthat/test_getSettingValue.R | 2 +- tests/testthat/test_getSettingsMetadata.R | 1 + 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/man/checkColumn.Rd b/man/checkColumn.Rd index cc5eac43..b8a4e6ab 100644 --- a/man/checkColumn.Rd +++ b/man/checkColumn.Rd @@ -31,18 +31,18 @@ testSettings$filters[[3]]<-list(value_col="NotAColumn",label="Invalid Column") #pass ($valid == TRUE) safetyGraphics:::checkColumn(key=list("id_col"), - settings=testSettings, adlbc) - + settings=testSettings, adlbc) + #pass safetyGraphics:::checkColumn(key=list("filters",1,"value_col"), - settings=testSettings, adlbc) - + settings=testSettings, adlbc) + #NULL column pass safetyGraphics:::checkColumn(key=list("filters",2,"value_col"), - settings=testSettings, adlbc) - + settings=testSettings, adlbc) + #invalid column fails safetyGraphics:::checkColumn(key=list("filters",3,"value_col"), - settings=testSettings, adlbc) + settings=testSettings, adlbc) } \keyword{internal} diff --git a/man/checkNumeric.Rd b/man/checkNumeric.Rd index ce7aded3..2dc139b9 100644 --- a/man/checkNumeric.Rd +++ b/man/checkNumeric.Rd @@ -22,10 +22,10 @@ Check that settings for mapping numeric data are associated with numeric columns \examples{ testSettings<-generateSettings(standard="AdAM") #pass ($valid == FALSE) -safetyGraphics:::checkNumeric(key=list("id_col"),settings=testSettings, data=adlbc) +safetyGraphics:::checkNumeric(key=list("id_col"),settings=testSettings, data=adlbc) #pass ($valid == TRUE) -safetyGraphics:::checkNumeric(key=list("value_col"),settings=testSettings, data=adlbc) +safetyGraphics:::checkNumeric(key=list("value_col"),settings=testSettings, data=adlbc) } \keyword{internal} diff --git a/man/generateSettings.Rd b/man/generateSettings.Rd index 2c14b94e..f5b634fa 100644 --- a/man/generateSettings.Rd +++ b/man/generateSettings.Rd @@ -32,17 +32,17 @@ The function is designed to work with the SDTM and AdAM CDISC(%filter(text_key=="measure_col")%>%select(valid)%>%unlist) }) @@ -38,7 +38,7 @@ 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_percent"]],1) - expect_equal(noFields[["valid_count"]],6) + expect_equal(noFields[["valid_count"]],7) }) test_that("invalid options throw errors",{ diff --git a/tests/testthat/test_generateSettings.R b/tests/testthat/test_generateSettings.R index f99a356c..87c2da36 100644 --- a/tests/testthat/test_generateSettings.R +++ b/tests/testthat/test_generateSettings.R @@ -1,6 +1,6 @@ context("Tests for the generateSettings() function") library(safetyGraphics) -setting_names<-c("id_col","value_col","measure_col","normal_col_low","normal_col_high","studyday_col", "visit_col", "visitn_col", "filters","group_cols", "measure_values", "baseline", "analysisFlag", "x_options", "y_options", "visit_window", "r_ratio_filter", "r_ratio_cut", "showTitle", "warningText") +setting_names<-c("id_col","value_col","measure_col","normal_col_low","normal_col_high","studyday_col", "visit_col", "visitn_col", "filters","group_cols", "measure_values", "baseline", "analysisFlag", "x_options", "y_options", "visit_window", "r_ratio_filter", "r_ratio_cut", "showTitle", "warningText", "unit_col", "start_value", "details", "missingValues") test_that("a list with the expected properties and structure is returned for all standards",{ diff --git a/tests/testthat/test_getSettingValue.R b/tests/testthat/test_getSettingValue.R index ea32427a..59f5ac00 100644 --- a/tests/testthat/test_getSettingValue.R +++ b/tests/testthat/test_getSettingValue.R @@ -14,7 +14,7 @@ test_that("different data types for `key` parameter work as expected",{ expect_equal(getSettingValue(key="id_col",settings=testSettings),"USUBJID") expect_equal(getSettingValue(key=c("measure_values","ALT"),settings=testSettings),"Aminotransferase, alanine (ALT)") expect_equal(getSettingValue(key=list("measure_values","ALT"),settings=testSettings),"Aminotransferase, alanine (ALT)") - expect_equal(getSettingValue(key=list("measure_values",2),settings=testSettings),"Aminotransferase, alanine (ALT)") + expect_equal(getSettingValue(key=list("measure_values",1),settings=testSettings),"Aminotransferase, alanine (ALT)") }) test_that("returns null if the setting isn't found",{ diff --git a/tests/testthat/test_getSettingsMetadata.R b/tests/testthat/test_getSettingsMetadata.R index 7d65b796..b88f5e1c 100644 --- a/tests/testthat/test_getSettingsMetadata.R +++ b/tests/testthat/test_getSettingsMetadata.R @@ -23,6 +23,7 @@ mergedMetadata = suppressWarnings(bind_rows( customMetadata%>%mutate(chart_edish= FALSE) )) + test_that("Default function copies the whole metadata dataframe",{ default<-safetyGraphics:::getSettingsMetadata(add_standards=FALSE) expect_is(default,"data.frame") From 3bd6d85f8a500acacd6bfb8c285ea7c04ad09373 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 29 Mar 2019 13:27:13 -0700 Subject: [PATCH 53/98] first buggy pass at automating settings generation --- .../modules/renderSettings/renderSettings.R | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index bccfb37b..b4f0ad9f 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -52,6 +52,8 @@ renderSettings <- function(input, output, session, data, settings, status){ #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 @@ -140,34 +142,16 @@ renderSettings <- function(input, output, session, data, settings, status){ ###################################################################### settings_new <- reactive({ + + keys<-input_names() %>% as.character() + values<- keys %>% map(~ifelse(is.null(input[[.x]]) , "", input[[.x]])) #Only capturing the first value in vectors ... + inputDF <- tibble(text_key=keys, customValue=values) %>% mutate(customValue= unlist(customValue)) + #print(inputDF) + settings <- generateSettings(custom_settings=inputDF) #need to add charts paramater + #print(settings) - settings <- list(id_col = input$id_col, - value_col = input$value_col, - measure_col = input$measure_col, - normal_col_low = input$normal_col_low, - normal_col_high = input$normal_col_high, - studyday_col = input$studyday_col, - visit_col = input$visit_col, - visitn_col = input$visitn_col, - measure_values = list(ALT = input$`measure_values--ALT`, - AST = input$`measure_values--AST`, - TB = input$`measure_values--TB`, - ALP = input$`measure_values--ALP`), - x_options = input$x_options, - y_options = input$y_options, - visit_window = input$visit_window, - r_ratio_filter = input$r_ratio_filter, - r_ratio_cut = input$r_ratio_cut, - showTitle = input$showTitle, - warningText = input$warningText, - unit_col = input$unit_col, - start_value = input$start_value, - details = as.list(input$details), - filters = as.list(input$filters), - group_cols = input$group_cols #as.list(input$group_cols) - ) - + # Need to reevaluate custom mappings below. if (! is.null(input$`baseline--values`)){ if (! input$`baseline--values`[1]==""){ settings$baseline <- list(value_col = input$`baseline--value_col`, From 1d99316785a10b8ce0129a2cb6ae804fc0b8a694 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 9 Apr 2019 16:29:31 -0400 Subject: [PATCH 54/98] append + hide/show tabs within navbarmenu --- inst/eDISH_app/global.R | 2 +- inst/eDISH_app/server.R | 103 +++++++++++++++++++++++++--------------- inst/eDISH_app/ui.R | 2 +- 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/inst/eDISH_app/global.R b/inst/eDISH_app/global.R index 46111ac8..17eea0f8 100644 --- a/inst/eDISH_app/global.R +++ b/inst/eDISH_app/global.R @@ -12,7 +12,7 @@ library(DT) library(haven) # create vector of all possible charts -allcharts <- c("edish","safetyhistogram") +all_charts <- c("edish","safetyhistogram") ## source modules source('modules/renderSettings/renderSettingsUI.R') diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 9e8dfdbc..a0c5ce2d 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -33,53 +33,82 @@ function(input, output, session){ status = reactive(dataUpload_out$status())) - - # ## this currently wipes away everything anytime there's a change in chart selections OR - # change in validation status - observe({ - - charts <- settings_new$charts() - - # remove whole navMenu and all existing chart tabs - removeTab(inputId="tabs", target="Charts") - appendTab(inputId = "tabs", navbarMenu("Charts")) - - # for each chart, append a new tab to the menu and place the module UI output - lapply(charts, function(chart){ - - status <- settings_new$status()[[chart]]$valid - if(status==TRUE){ - tab_title <- HTML(paste(chart, icon("check", class="ok"))) - } else { - tab_title <- HTML(paste(chart, icon("times", class="notok"))) - } - - tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab - #tabid <- paste0(chart, "_tab_title") - tabcode <- tabPanel(title = tab_title, - # title = htmlOutput(tabid), - tabfun(paste0("chart", chart))) - - appendTab(inputId = "tabs", - tabcode, + + # set up all tabs from the start (allcharts defined in global.R) + # generated from server.R so we can do this dynamically in future.. + for (chart in all_charts){ + + tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab + tabid <- paste0(chart, "_tab_title") + + appendTab(inputId = "nav_id", + tab = tabPanel(title = chart, + tabfun(paste0("chart", chart))), menuName = "Charts") - }) - }) + } + # hide/show chart tabs in response to user selections + observe({ - # call all chart modules - for (chart in allcharts){ + selected_charts <- settings_new$charts() + unselected_charts <- all_charts[!all_charts %in% selected_charts] + + for(chart in unselected_charts){ + hideTab(inputId = "nav_id", target = chart) + } + for(chart in selected_charts){ + showTab(inputId = "nav_id", target = chart) + } + }) - modfun <- match.fun(paste0("render_", chart, "_chart")) + # ## this currently wipes away everything anytime there's a change in chart selections OR + # change in validation status + # observe({ + # + # charts <- settings_new$charts() + # + # # remove whole navMenu and all existing chart tabs + # removeTab(inputId="tabs", target="Charts") + # appendTab(inputId = "tabs", navbarMenu("Charts")) + # + # # for each chart, append a new tab to the menu and place the module UI output + # lapply(charts, function(chart){ + # + # status <- settings_new$status()[[chart]]$valid + # if(status==TRUE){ + # tab_title <- HTML(paste(chart, icon("check", class="ok"))) + # } else { + # tab_title <- HTML(paste(chart, icon("times", class="notok"))) + # } + # + # tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab + # #tabid <- paste0(chart, "_tab_title") + # tabcode <- tabPanel(title = tab_title, + # # title = htmlOutput(tabid), + # tabfun(paste0("chart", chart))) + # + # appendTab(inputId = "tabs", + # tabcode, + # menuName = "Charts") + # }) + # }) + # + # + + # call all chart modules + # # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and # the value for "valid" doesn't always get passed directly. - # Moving to renderChart module will hopefully help here - callModule(module = modfun, + # Moving to renderChart module will hopefully help here for (chart in all_charts){ + + modfun <- match.fun(paste0("render_", chart, "_chart")) + + callModule(module = modfun, id = paste0("chart", chart), data = reactive(dataUpload_out$data_selected()), settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()[[chart]]$valid)) ## bad + valid = reactive(settings_new$status()[[chart]]$valid)) } diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index 91f4746f..3934cad0 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -5,7 +5,7 @@ tagList( tags$head( tags$link(rel = "stylesheet", type = "text/css", href = "index.css") ), - navbarPage("eDISH Shiny app", id="tabs", + navbarPage("eDISH Shiny app", id="nav_id", tabPanel(title = htmlOutput("data_tab_title"), dataUploadUI("datatab") ), From df0a6602c34ed85f2a87811f3bb8f1a74f371681 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Tue, 9 Apr 2019 21:45:09 -0700 Subject: [PATCH 55/98] remove stray brace --- inst/eDISH_app/server.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index a0c5ce2d..65552670 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -109,7 +109,7 @@ function(input, output, session){ data = reactive(dataUpload_out$data_selected()), settings = reactive(settings_new$settings()), valid = reactive(settings_new$status()[[chart]]$valid)) - } + session$onSessionEnded(stopApp) From 5c3d67e6f6a29f9f80814214d082448a0b32994d Mon Sep 17 00:00:00 2001 From: jwildfire Date: Tue, 9 Apr 2019 22:06:45 -0700 Subject: [PATCH 56/98] code formatting/comments --- inst/eDISH_app/server.R | 149 ++++++++++++++++------------------------ inst/eDISH_app/ui.R | 31 +++++---- 2 files changed, 79 insertions(+), 101 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 65552670..8e44e9bc 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -6,112 +6,83 @@ # indicating whether user has satisfied requirements of that tab function(input, output, session){ - - - # run dataUpload module + + ############################################################## + # initialize dataUpload module # # returns selected dataset, settings, and validation status + ############################################################## dataUpload_out <- callModule(dataUpload, "datatab") - # 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"))) - span(tagList("Data", icon("check", class="ok"))) - }) - - # based on selected data set & generated/selected settings obj, generate settings page. + ############################################################## + # Initialize Settings Module + # + # generate settings page based on selected data set & generated/selected settings obj # # NOTE: module is being triggered when selected dataset changes OR when settings list changes # this could cause the module to trigger twice unecessarily in some cases because the settings are generated # AFTER the data is changed. # - # reutrns updated settings and validation status - settings_new <- callModule(renderSettings, "settingsUI", - data = reactive(dataUpload_out$data_selected()), - settings = reactive(dataUpload_out$settings()), - status = reactive(dataUpload_out$status())) + # returns updated settings and validation status + ############################################################## + + settings_new <- callModule( + renderSettings, + "settingsUI", + data = reactive(dataUpload_out$data_selected()), + settings = reactive(dataUpload_out$settings()), + status = reactive(dataUpload_out$status()) + ) - - # set up all tabs from the start (allcharts defined in global.R) - # generated from server.R so we can do this dynamically in future.. - for (chart in all_charts){ + ############################################################## + # Initialize Charts Modules + ############################################################## + + # set up all chart tabs from the start (allcharts defined in global.R) + # generated from server.R so we can do this dynamically in future.. + for (chart in all_charts){ - tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab - tabid <- paste0(chart, "_tab_title") + tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab + tabid <- paste0(chart, "_tab_title") - appendTab(inputId = "nav_id", - tab = tabPanel(title = chart, - tabfun(paste0("chart", chart))), - menuName = "Charts") - } - - # hide/show chart tabs in response to user selections - observe({ + appendTab( + inputId = "nav_id", + tab = tabPanel( + title = chart, + tabfun(paste0("chart", chart)) + ), + menuName = "Charts" + ) + } - selected_charts <- settings_new$charts() - unselected_charts <- all_charts[!all_charts %in% selected_charts] + # hide/show chart tabs in response to user selections + observe({ + selected_charts <- settings_new$charts() + unselected_charts <- all_charts[!all_charts %in% selected_charts] - for(chart in unselected_charts){ - hideTab(inputId = "nav_id", target = chart) - } - for(chart in selected_charts){ - showTab(inputId = "nav_id", target = chart) - } - }) - - - # ## this currently wipes away everything anytime there's a change in chart selections OR - # change in validation status - # observe({ - # - # charts <- settings_new$charts() - # - # # remove whole navMenu and all existing chart tabs - # removeTab(inputId="tabs", target="Charts") - # appendTab(inputId = "tabs", navbarMenu("Charts")) - # - # # for each chart, append a new tab to the menu and place the module UI output - # lapply(charts, function(chart){ - # - # status <- settings_new$status()[[chart]]$valid - # if(status==TRUE){ - # tab_title <- HTML(paste(chart, icon("check", class="ok"))) - # } else { - # tab_title <- HTML(paste(chart, icon("times", class="notok"))) - # } - # - # tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab - # #tabid <- paste0(chart, "_tab_title") - # tabcode <- tabPanel(title = tab_title, - # # title = htmlOutput(tabid), - # tabfun(paste0("chart", chart))) - # - # appendTab(inputId = "tabs", - # tabcode, - # menuName = "Charts") - # }) - # }) - # - # + for(chart in unselected_charts){ + hideTab(inputId = "nav_id", target = chart) + } + for(chart in selected_charts){ + showTab(inputId = "nav_id", target = chart) + } + }) - # call all chart modules - # - # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and - # the value for "valid" doesn't always get passed directly. - # Moving to renderChart module will hopefully help here for (chart in all_charts){ - - modfun <- match.fun(paste0("render_", chart, "_chart")) - - callModule(module = modfun, - id = paste0("chart", chart), - data = reactive(dataUpload_out$data_selected()), - settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()[[chart]]$valid)) + # call all chart modules + # + # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and + # the value for "valid" doesn't always get passed directly. + # Moving to renderChart module will hopefully help here for (chart in all_charts){ - + modfun <- match.fun(paste0("render_", chart, "_chart")) + callModule( + module = modfun, + id = paste0("chart", chart), + data = reactive(dataUpload_out$data_selected()), + settings = reactive(settings_new$settings()), + valid = reactive(settings_new$status()[[chart]]$valid) + ) session$onSessionEnded(stopApp) - } diff --git a/inst/eDISH_app/ui.R b/inst/eDISH_app/ui.R index 3934cad0..2506423a 100644 --- a/inst/eDISH_app/ui.R +++ b/inst/eDISH_app/ui.R @@ -3,17 +3,24 @@ tagList( useShinyjs(), tags$head( - tags$link(rel = "stylesheet", type = "text/css", href = "index.css") + tags$link( + rel = "stylesheet", + type = "text/css", + href = "index.css") ), - navbarPage("eDISH Shiny app", id="nav_id", - tabPanel(title = htmlOutput("data_tab_title"), - dataUploadUI("datatab") - ), - tabPanel(title = "Settings", - fluidPage( - renderSettingsUI("settingsUI") - ) - ), - navbarMenu("Charts") -) + navbarPage( + "eDISH Shiny app", + id="nav_id", + tabPanel( + title = "Data", + dataUploadUI("datatab") + ), + tabPanel( + title = "Settings", + fluidPage( + renderSettingsUI("settingsUI") + ) + ), + navbarMenu("Charts") + ) ) From ddfdad71bfe1d141d41e01656a12964a0b913ee3 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 10 Apr 2019 09:24:57 -0700 Subject: [PATCH 57/98] clean indent --- .../modules/renderSettings/renderSettings.R | 183 +++++++++--------- 1 file changed, 95 insertions(+), 88 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index b4f0ad9f..39f2bd7e 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -6,9 +6,9 @@ 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. -#' +#' +#' 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. @@ -27,11 +27,11 @@ source("modules/renderSettings/util/updateSettingStatus.R") #' - 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. +#' (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 +#' (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 @@ -39,21 +39,21 @@ source("modules/renderSettings/util/updateSettingStatus.R") #' @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: +#' @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 @@ -65,123 +65,130 @@ renderSettings <- function(input, output, session, data, settings, status){ 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) - + 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) - + ###################################################################### - # Update field level inputs + # 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({ - - field_rows <- getSettingsMetadata(charts=input$charts, - filter_expr = field_mapping==TRUE) - - if(!is.null(field_rows)){ - column_keys <- field_rows %>% - pull(field_column_key) %>% - unique %>% - as.list() - - 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) - - - # 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]]=="") - } - 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(""); - }'))) - } - } - } - } + field_rows <- getSettingsMetadata( + charts=input$charts, + filter_expr = field_mapping==TRUE ) - }) - } - }) - - + + if(!is.null(field_rows)){ + column_keys <- field_rows %>% + pull(field_column_key) %>% + unique %>% + as.list() + + 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 + ) + + # 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]]=="") + } + + 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("");}') + ) + ) #update SelectizeInput + } #for loop + } #if #2 + } #if #1 + } #observeEvent (inner) + ) #observeEvent (outer) + }) #lapply + } #if(!is.null) + }) #observe + + ###################################################################### # Fill settings object based on selections - # + # # update is triggered by any of the input selections changing ###################################################################### - + settings_new <- reactive({ - + keys<-input_names() %>% as.character() - values<- keys %>% map(~ifelse(is.null(input[[.x]]) , "", input[[.x]])) #Only capturing the first value in vectors ... + values<- keys %>% map(~ifelse(is.null(input[[.x]]) , "", input[[.x]])) #Only capturing the first value in vectors ... inputDF <- tibble(text_key=keys, customValue=values) %>% mutate(customValue= unlist(customValue)) - + #print(inputDF) settings <- generateSettings(custom_settings=inputDF) #need to add charts paramater #print(settings) - - # Need to reevaluate custom mappings below. + + # Need to reevaluate custom mappings below. if (! is.null(input$`baseline--values`)){ if (! input$`baseline--values`[1]==""){ settings$baseline <- list(value_col = input$`baseline--value_col`, values = input$`baseline--values`) } } - + if (! is.null(input$`analysisFlag--values`)){ if (! input$`analysisFlag--values`[1]==""){ settings$analysisFlag <- list(value_col = input$`analysisFlag--value_col`, values = input$`analysisFlag--values`) } } - + return(settings) }) - - + + ###################################################################### # validate new settings # the validation is run every time there is a change in data and/or settings. # ###################################################################### - status_new <- reactive({ + status_new <- reactive({ req(data()) req(settings_new()) name <- rev(isolate(input_names()))[1] settings_new <- settings_new() - + for (i in names(settings_new)){ if (!is.null(settings_new[[i]])){ if (settings_new[[i]][1]==""){ @@ -189,29 +196,29 @@ renderSettings <- function(input, output, session, data, settings, status){ } } } - + out <- list() - + charts <- isolate(input$charts) for (chart in charts){ out[[chart]] <- validateSettings(data(), settings_new, chart=chart) } - + return(out) }) - - + + ###################################################################### # Setting validation status information ###################################################################### status_df <- reactive({ req(status_new()) - - #status_new()$checks %>% + + #status_new()$checks %>% flatten(status_new()) %>% - keep(., names(.)=="checks") %>% - bind_rows() %>% - unique %>% + keep(., names(.)=="checks") %>% + bind_rows() %>% + unique %>% group_by(text_key) %>% mutate(num_fail = sum(valid==FALSE)) %>% mutate(icon = ifelse(num_fail==0, "",""))%>% @@ -222,12 +229,12 @@ renderSettings <- function(input, output, session, data, settings, status){ TRUE ~ paste(num_fail, "failed checks.") )) %>% select(text_key, icon, message_long, message_short, num_fail) %>% - unique + unique }) - + # for shiny tests exportTestValues(status_df = { status_df() }) - + ###################################################################### # print validation messages ###################################################################### @@ -244,10 +251,10 @@ renderSettings <- function(input, output, session, data, settings, status){ } }) - + ### return updated settings and status to global env. return(list(charts = reactive(input$charts), settings = reactive(settings_new()), status = reactive(status_new()))) - + } From 40eea36e04e85cefcad8fdfd41a6ce9a73ad5afb Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 10 Apr 2019 09:29:57 -0700 Subject: [PATCH 58/98] more formatting --- .../modules/renderSettings/renderSettings.R | 88 +++++++++++++------ 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 39f2bd7e..907f6c40 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -63,22 +63,47 @@ renderSettings <- function(input, output, session, data, settings, status){ output$data_mapping_ui <- renderUI({ req(input$charts) - tagList(createSettingsUI(data=data(), settings = settings(), setting_cat_val = "data", charts=input$charts, ns=ns)) + 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)) + 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)) + tagList( + createSettingsUI( + data=data(), + settings = settings(), + setting_cat_val = "appearance", + charts=input$charts, + ns=ns + ) + ) }) - outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE) + outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) + outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) + outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE) ###################################################################### # Update field level inputs @@ -88,7 +113,6 @@ renderSettings <- function(input, output, session, data, settings, status){ ###################################################################### observe({ - field_rows <- getSettingsMetadata( charts=input$charts, filter_expr = field_mapping==TRUE @@ -141,7 +165,6 @@ renderSettings <- function(input, output, session, data, settings, status){ } #if(!is.null) }) #observe - ###################################################################### # Fill settings object based on selections # @@ -161,15 +184,19 @@ renderSettings <- function(input, output, session, data, settings, status){ # Need to reevaluate custom mappings below. if (! is.null(input$`baseline--values`)){ if (! input$`baseline--values`[1]==""){ - settings$baseline <- list(value_col = input$`baseline--value_col`, - values = input$`baseline--values`) + settings$baseline <- list( + value_col = input$`baseline--value_col`, + values = input$`baseline--values` + ) } } if (! is.null(input$`analysisFlag--values`)){ if (! input$`analysisFlag--values`[1]==""){ - settings$analysisFlag <- list(value_col = input$`analysisFlag--value_col`, - values = input$`analysisFlag--values`) + settings$analysisFlag <- list( + value_col = input$`analysisFlag--value_col`, + values = input$`analysisFlag--values` + ) } } @@ -214,7 +241,6 @@ renderSettings <- function(input, output, session, data, settings, status){ status_df <- reactive({ req(status_new()) - #status_new()$checks %>% flatten(status_new()) %>% keep(., names(.)=="checks") %>% bind_rows() %>% @@ -222,12 +248,14 @@ renderSettings <- function(input, output, session, data, settings, status){ 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.") - )) %>% + 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, icon, message_long, message_short, num_fail) %>% unique }) @@ -240,21 +268,27 @@ renderSettings <- function(input, output, session, data, settings, status){ ###################################################################### 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) + 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(charts = reactive(input$charts), - settings = reactive(settings_new()), - status = reactive(status_new()))) - + return( + list( + charts = reactive(input$charts), + settings = reactive(settings_new()), + status = reactive(status_new()) + ) + ) } From aaf3f352e4e4c556647dd7a36c7e507cfdee5431 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 10 Apr 2019 11:30:27 -0700 Subject: [PATCH 59/98] fixing settings merge ... --- R/generateSettings.R | 9 ++++-- .../modules/renderSettings/renderSettings.R | 30 ++++++++++++++----- inst/eDISH_app/server.R | 2 +- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index 7a2963c7..49761ea4 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -98,7 +98,8 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par key_values$customValue<-NA } - key_values<-key_values %>% mutate(value=ifelse(is.na(.data$customValue), .data$default, .data$customValue)) + key_values<- key_values %>% mutate(value=ifelse(is.na(.data$customValue),.data$default,.data$customValue)) + ############################################################################# # create shell settings object @@ -108,11 +109,15 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par ######################################################################################### # populate the shell settings by looping through key_values and apply them to the shell ######################################################################################### + print(key_values) +# print(key_values[1,"default"]) for(row in 1:nrow(key_values)){ + value<-key_values[row, "value"][[1]] + #finalValue<-ifelse(length(value)>1,value,value[[1]]) shell<-setSettingsValue( settings = shell, key = textKeysToList(key_values[row,"text_key"])[[1]], - value = key_values[row, "value"][[1]] + value = value ) } diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 907f6c40..7e843b6c 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -173,13 +173,29 @@ renderSettings <- function(input, output, session, data, settings, status){ settings_new <- reactive({ - keys<-input_names() %>% as.character() - values<- keys %>% map(~ifelse(is.null(input[[.x]]) , "", input[[.x]])) #Only capturing the first value in vectors ... - inputDF <- tibble(text_key=keys, customValue=values) %>% mutate(customValue= unlist(customValue)) - - #print(inputDF) - settings <- generateSettings(custom_settings=inputDF) #need to add charts paramater - #print(settings) + getValues <- function(x){ + if (is.null(input[[x]])){ + return(NULL) + } else{ + return(input[[x]]) + } + } + req(input_names()) + keys <- input_names() + # print(keys) + values<- keys %>% map(~getValues(.x)) + print(values) + inputDF <- tibble(text_key=keys, customValue=values) %>% + filter(!is.null(customValue[[1]])) + print(inputDF) + # print(inputDF) + if(nrow(inputDF)>0){ + settings <- generateSettings(custom_settings=inputDF, charts=input$charts) + }else{ + settings<- generateSettings(charts=input$charts) + } + + # print(settings) # Need to reevaluate custom mappings below. if (! is.null(input$`baseline--values`)){ diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 8e44e9bc..735838ae 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -26,7 +26,7 @@ function(input, output, session){ # returns updated settings and validation status ############################################################## - settings_new <- callModule( +settings_new <- callModule( renderSettings, "settingsUI", data = reactive(dataUpload_out$data_selected()), From 7143a273bcab238af177b39a207901ae41661b6a Mon Sep 17 00:00:00 2001 From: jwildfire Date: Thu, 11 Apr 2019 11:11:59 -0700 Subject: [PATCH 60/98] more settings mapping tweaks --- R/generateSettings.R | 19 ++++--- R/getSettingsMetadata.R | 57 +++++++++---------- .../modules/renderChart/render_edish_chart.R | 26 ++++----- .../render_safetyhistogram_chart.R | 28 ++++----- .../modules/renderSettings/renderSettings.R | 39 +++++++------ 5 files changed, 84 insertions(+), 85 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index 49761ea4..21966a91 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -68,7 +68,6 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par if(partial){ dataDefaults <-dataDefaults%>%filter(.data$text_key %in% partial_keys) } - ############################################################################# # get keys & default values for settings not using a data standard ############################################################################# @@ -100,7 +99,6 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par key_values<- key_values %>% mutate(value=ifelse(is.na(.data$customValue),.data$default,.data$customValue)) - ############################################################################# # create shell settings object ############################################################################# @@ -109,17 +107,20 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par ######################################################################################### # populate the shell settings by looping through key_values and apply them to the shell ######################################################################################### - print(key_values) -# print(key_values[1,"default"]) + #print(key_values) for(row in 1:nrow(key_values)){ - value<-key_values[row, "value"][[1]] - #finalValue<-ifelse(length(value)>1,value,value[[1]]) + text_key<-key_values[row,]%>%pull("text_key") + key<- textKeysToList(text_key)[[1]] + type <- safetyGraphics::getSettingsMetadata(text_keys=text_key,cols="setting_type") + value <- key_values[row,"value"][[1]] + finalValue <- ifelse(type=="vector",as.list(value[[1]]),value[[1]]) + #print(paste(text_key," (",type,"):",toString(value),typeof(value),length(value),"->",finalValue,typeof(finalValue),length(finalValue))) shell<-setSettingsValue( settings = shell, - key = textKeysToList(key_values[row,"text_key"])[[1]], - value = value + key = key, + value = finalValue ) } - + #print(shell) return(shell) } diff --git a/R/getSettingsMetadata.R b/R/getSettingsMetadata.R index 7ae8aec8..31463e72 100644 --- a/R/getSettingsMetadata.R +++ b/R/getSettingsMetadata.R @@ -1,55 +1,54 @@ #' Get metadata about chart settings #' -#' Retrieve specified metadata about chart settings from the data/settingsMetadata.Rda file. -#' +#' Retrieve specified metadata about chart settings from the data/settingsMetadata.Rda file. +#' #' @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 cols optional vector of columns to return from the metadata. All columns returned by default. #' @param metadata metadata data frame to be queried -#' +#' #' @return dataframe with the requested metadata or single metadata value -#' -#' @examples -#' safetyGraphics:::getSettingsMetadata() +#' +#' @examples +#' safetyGraphics:::getSettingsMetadata() #' # Returns a full copy of settingsMetadata.Rda -#' -#' safetyGraphics:::getSettingsMetadata(text_keys=c("id_col")) +#' +#' safetyGraphics:::getSettingsMetadata(text_keys=c("id_col")) #' # returns a dataframe with a single row with metadata for the id_col setting -#' -#' safetyGraphics:::getSettingsMetadata(text_keys=c("id_col"), cols=c("label")) -#' # returns the character value for the specified row. -#' +#' +#' safetyGraphics:::getSettingsMetadata(text_keys=c("id_col"), cols=c("label")) +#' # returns the character value for the specified row. +#' #' @importFrom stringr str_subset #' @importFrom magrittr "%>%" #' @import dplyr #' @importFrom rlang .data -#' +#' #' @export getSettingsMetadata<-function(charts=NULL, text_keys=NULL, cols=NULL, filter_expr=NULL, add_standards=TRUE, metadata = safetyGraphics::settingsMetadata){ - 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) if(!is.null(charts)){ #Don't do anything if charts isn't specified stopifnot(typeof(charts) == "character") - + # get list of all chart flags in the data chart_columns <- tolower(str_subset(all_columns, "^chart_")); - + # get a list of chart flags matching the request charts<-tolower(charts) matched_chart_columns <- intersect(chart_columns, paste0("chart_",charts)) - #filter based + #filter based if(length(matched_chart_columns)==0){ return(NULL) }else{ @@ -57,39 +56,39 @@ getSettingsMetadata<-function(charts=NULL, text_keys=NULL, cols=NULL, filter_exp md<-md%>%filter_at(vars(matched_chart_columns),any_vars(.)) } } - - #filter the metadata based on the text_keys option (if any) + + #filter the metadata based on the text_keys option (if any) if(!is.null(text_keys)){ stopifnot(typeof(text_keys) == "character") md<-md%>%filter(tolower(.data$text_key) %in% tolower(text_keys)) } - - #filter the metadata based on a the filter expression + + #filter the metadata based on a the filter expression filter_expr <- enexpr(filter_expr) if(!is.null(filter_expr)){ stopifnot(typeof(filter_expr) %in% c("language","symbol")) md<-md %>% filter(!!filter_expr) } - + #subset the metadata columns returned based on the metadata_columns option (if any) if(!is.null(cols)){ stopifnot(typeof(cols) =="character") valid_cols <- intersect(cols, names(md)) md<-md%>%select(valid_cols) } - + #coerce factors to character if(dim(md)[2]>0){ i <- sapply(md, is.factor) md[i] <- lapply(md[i], as.character) } - + #return the requested metadata if(dim(md)[1]==0 | dim(md)[2]==0){ #return null if no rows or no columns are selected return(NULL) }else if(dim(md)[2]==1){ #return a vector if there is only a single columns specified return(md[[1]]) }else{ #otherwise return the whole data frame - return(md) + return(md) } } diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R index c7da2866..d0aba02a 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -1,23 +1,23 @@ #' Render eDISH chart - server code -#' -#' This module creates the Chart tab for the Shiny app, which contains the interactive eDISH graphic. -#' -#' Workflow: +#' +#' 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 data A data frame #' @param valid A logical indicating whether data/settings combination is valid for chart - + render_edish_chart <- function(input, output, session, data, settings, valid){ - + ns <- session$ns - + # render eDISH chart if settings pass validation output$chart <- renderEDISH({ req(data()) @@ -30,8 +30,8 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ # return() # } }) - - + + # insert export chart button if settings pass validation # remove button if validation fails observeEvent(valid(), { @@ -72,5 +72,5 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ ) } ) - -} \ No newline at end of file + +} diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R index 24685959..1f577343 100644 --- a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R @@ -1,33 +1,33 @@ #' Render Safety Histogram chart - server code -#' -#' This module creates the Chart tab for the Shiny app, which contains the interactive Histogram graphic. -#' -#' Workflow: +#' +#' This module creates the Chart tab for the Shiny app, which contains the interactive Histogram graphic. +#' +#' Workflow: #' (1) A change in `data`, `settings`, or `valid` invalidates the safety histogram chart output #' (2) Upon a change in `valid`, the export chart functionality is conditionally made available or unavailable to user -#' +#' #' @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 data A data frame #' @param valid A logical indicating whether data/settings combination is valid for chart render_safetyhistogram_chart <- function(input, output, session, data, settings, valid){ - + ns <- session$ns - - + + # render eDISH chart if settings pass validation output$chart <- renderSafetyHistogram({ req(data()) req(settings()) - + # if (valid()==TRUE){ - trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) + #trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) safetyHistogram(data = data(), settings = settings()) # } else{ # return() # } - }) - -} \ No newline at end of file + }) + +} diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 7e843b6c..3b42fc7f 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -182,13 +182,14 @@ renderSettings <- function(input, output, session, data, settings, status){ } req(input_names()) keys <- input_names() - # print(keys) + values<- keys %>% map(~getValues(.x)) - print(values) + inputDF <- tibble(text_key=keys, customValue=values) %>% filter(!is.null(customValue[[1]])) + print(inputDF) - # print(inputDF) + if(nrow(inputDF)>0){ settings <- generateSettings(custom_settings=inputDF, charts=input$charts) }else{ @@ -198,23 +199,21 @@ renderSettings <- function(input, output, session, data, settings, status){ # print(settings) # Need to reevaluate custom mappings below. - if (! is.null(input$`baseline--values`)){ - if (! input$`baseline--values`[1]==""){ - settings$baseline <- list( - value_col = input$`baseline--value_col`, - values = input$`baseline--values` - ) - } - } - - if (! is.null(input$`analysisFlag--values`)){ - if (! input$`analysisFlag--values`[1]==""){ - settings$analysisFlag <- list( - value_col = input$`analysisFlag--value_col`, - values = input$`analysisFlag--values` - ) - } - } +# if (! is.null(input$`baseline--values`)){ +# if (! input$`baseline--values`[1]==""){ +# settings$baseline <- list( +# value_col = input$`baseline--value_col`, +# ) +# } +# +# if (! is.null(input$`analysisFlag--values`)){ +# if (! input$`analysisFlag--values`[1]==""){ +# settings$analysisFlag <- list( +# value_col = input$`analysisFlag--value_col`, +# values = input$`analysisFlag--values` +# ) +# } +# } return(settings) }) From 303b2b056bd568efd2f02f077984c7f7b8d91d75 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 12 Apr 2019 11:29:43 -0400 Subject: [PATCH 61/98] move code that coerces "" settings to NULL's --- .../modules/renderSettings/renderSettings.R | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 3b42fc7f..6a7bcaa7 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -215,6 +215,15 @@ renderSettings <- function(input, output, session, data, settings, status){ # } # } + + for (i in names(settings)){ + if (!is.null(settings[[i]])){ + if (settings[[i]][1]==""){ + settings[i] <- list(NULL) + } + } + } + return(settings) }) @@ -231,13 +240,13 @@ renderSettings <- function(input, output, session, data, settings, status){ name <- rev(isolate(input_names()))[1] settings_new <- settings_new() - for (i in names(settings_new)){ - if (!is.null(settings_new[[i]])){ - if (settings_new[[i]][1]==""){ - settings_new[i] <- list(NULL) - } - } - } + # for (i in names(settings_new)){ + # if (!is.null(settings_new[[i]])){ + # if (settings_new[[i]][1]==""){ + # settings_new[i] <- list(NULL) + # } + # } + # } out <- list() From 28d4a8874db5c4447852bf26e2b0527bed3e8c07 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 12 Apr 2019 11:31:19 -0400 Subject: [PATCH 62/98] convert dataDefault to tibble; tweak final setting value --- R/generateSettings.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index 21966a91..70d55507 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -60,7 +60,8 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par filter(.data$setting_required)%>% select(-.data$setting_required)%>% rename("dataDefault" = standard)%>% - filter(.data$dataDefault != '') + filter(.data$dataDefault != '') %>% + as_tibble }else{ dataDefaults<-tibble(text_key=character(),dataDefault=character(), .rows=0) } @@ -113,7 +114,8 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par key<- textKeysToList(text_key)[[1]] type <- safetyGraphics::getSettingsMetadata(text_keys=text_key,cols="setting_type") value <- key_values[row,"value"][[1]] - finalValue <- ifelse(type=="vector",as.list(value[[1]]),value[[1]]) + # finalValue <- ifelse(type=="vector",as.list(value[[1]]),value[[1]]) + finalValue <- value[[1]] #print(paste(text_key," (",type,"):",toString(value),typeof(value),length(value),"->",finalValue,typeof(finalValue),length(finalValue))) shell<-setSettingsValue( settings = shell, From 571dfc96094392af19474bde5d0fd0ae66428362 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 12 Apr 2019 11:31:48 -0400 Subject: [PATCH 63/98] loop thru chart modules --- inst/eDISH_app/server.R | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 735838ae..dd32bae8 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -75,14 +75,19 @@ settings_new <- callModule( # the value for "valid" doesn't always get passed directly. # Moving to renderChart module will hopefully help here for (chart in all_charts){ - modfun <- match.fun(paste0("render_", chart, "_chart")) - callModule( - module = modfun, - id = paste0("chart", chart), - data = reactive(dataUpload_out$data_selected()), - settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()[[chart]]$valid) - ) + for (chart in all_charts){ + + modfun <- match.fun(paste0("render_", chart, "_chart")) + callModule( + module = modfun, + id = paste0("chart", chart), + data = reactive(dataUpload_out$data_selected()), + settings = reactive(settings_new$settings()), + valid = reactive(settings_new$status()[[chart]]$valid) + ) + + } + session$onSessionEnded(stopApp) } From b32bebddfe275a374965f5c7c55d280c782bcc11 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 12 Apr 2019 10:45:26 -0700 Subject: [PATCH 64/98] deal with empty string --- R/generateSettings.R | 14 +++++- .../modules/renderSettings/renderSettings.R | 48 ++----------------- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/R/generateSettings.R b/R/generateSettings.R index 70d55507..9a52f5bb 100644 --- a/R/generateSettings.R +++ b/R/generateSettings.R @@ -60,7 +60,7 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par filter(.data$setting_required)%>% select(-.data$setting_required)%>% rename("dataDefault" = standard)%>% - filter(.data$dataDefault != '') %>% + filter(.data$dataDefault != '') %>% as_tibble }else{ dataDefaults<-tibble(text_key=character(),dataDefault=character(), .rows=0) @@ -114,8 +114,8 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par key<- textKeysToList(text_key)[[1]] type <- safetyGraphics::getSettingsMetadata(text_keys=text_key,cols="setting_type") value <- key_values[row,"value"][[1]] - # finalValue <- ifelse(type=="vector",as.list(value[[1]]),value[[1]]) finalValue <- value[[1]] + #print(paste(text_key," (",type,"):",toString(value),typeof(value),length(value),"->",finalValue,typeof(finalValue),length(finalValue))) shell<-setSettingsValue( settings = shell, @@ -123,6 +123,16 @@ generateSettings <- function(standard="None", charts=NULL, useDefaults=TRUE, par value = finalValue ) } + + #Coerce empty string to NULL + for (i in names(shell)){ + if (!is.null(shell[[i]])){ + if (shell[[i]][1]==""){ + shell[i] <- list(NULL) + } + } + } + #print(shell) return(shell) } diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 6a7bcaa7..a0019af9 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -188,42 +188,12 @@ renderSettings <- function(input, output, session, data, settings, status){ inputDF <- tibble(text_key=keys, customValue=values) %>% filter(!is.null(customValue[[1]])) - print(inputDF) - - if(nrow(inputDF)>0){ - settings <- generateSettings(custom_settings=inputDF, charts=input$charts) - }else{ - settings<- generateSettings(charts=input$charts) - } - - # print(settings) - - # Need to reevaluate custom mappings below. -# if (! is.null(input$`baseline--values`)){ -# if (! input$`baseline--values`[1]==""){ -# settings$baseline <- list( -# value_col = input$`baseline--value_col`, -# ) -# } -# -# if (! is.null(input$`analysisFlag--values`)){ -# if (! input$`analysisFlag--values`[1]==""){ -# settings$analysisFlag <- list( -# value_col = input$`analysisFlag--value_col`, -# values = input$`analysisFlag--values` -# ) -# } -# } - - - for (i in names(settings)){ - if (!is.null(settings[[i]])){ - if (settings[[i]][1]==""){ - settings[i] <- list(NULL) - } - } + if(nrow(inputDF)>0){ + settings <- generateSettings(custom_settings=inputDF, charts=input$charts) + }else{ + settings<- generateSettings(charts=input$charts) } - + return(settings) }) @@ -240,14 +210,6 @@ renderSettings <- function(input, output, session, data, settings, status){ name <- rev(isolate(input_names()))[1] settings_new <- settings_new() - # for (i in names(settings_new)){ - # if (!is.null(settings_new[[i]])){ - # if (settings_new[[i]][1]==""){ - # settings_new[i] <- list(NULL) - # } - # } - # } - out <- list() charts <- isolate(input$charts) From cb7eb96e88fb2afa72595d674c075685fa03baa7 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 12 Apr 2019 13:23:01 -0700 Subject: [PATCH 65/98] fix input_names() reactive. fix #229 --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index a0019af9..e2479a96 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -50,9 +50,7 @@ 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")}) - - + input_names <- reactive({safetyGraphics:::getSettingsMetadata(charts=input$charts, cols="text_key")}) ###################################################################### # create settings UI @@ -180,14 +178,12 @@ renderSettings <- function(input, output, session, data, settings, status){ return(input[[x]]) } } - req(input_names()) - keys <- input_names() + keys <- input_names() values<- keys %>% map(~getValues(.x)) inputDF <- tibble(text_key=keys, customValue=values) %>% filter(!is.null(customValue[[1]])) - if(nrow(inputDF)>0){ settings <- generateSettings(custom_settings=inputDF, charts=input$charts) }else{ From 2b65e3b2fc2d3a580f7a02a4540b23e69df69478 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Fri, 12 Apr 2019 13:25:52 -0700 Subject: [PATCH 66/98] re-add deleted req() --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index e2479a96..68983841 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -179,6 +179,7 @@ renderSettings <- function(input, output, session, data, settings, status){ } } + req(input_names()) keys <- input_names() values<- keys %>% map(~getValues(.x)) From bdc2392f0e2bbe10f864df5010b705a39ae01a30 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 15 Apr 2019 14:03:17 -0400 Subject: [PATCH 67/98] fix #232 --- .../modules/renderSettings/renderSettings.R | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 68983841..de247792 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -60,13 +60,14 @@ renderSettings <- function(input, output, session, data, settings, status){ ###################################################################### output$data_mapping_ui <- renderUI({ - req(input$charts) + charts <- isolate(input$charts) + req(charts) tagList( createSettingsUI( data=data(), settings = settings(), setting_cat_val = "data", - charts=input$charts, + charts=charts, ns=ns ) ) @@ -74,31 +75,56 @@ renderSettings <- function(input, output, session, data, settings, status){ output$measure_settings_ui <- renderUI({ - req(input$charts) + charts <- isolate(input$charts) + req(charts) tagList( createSettingsUI( data=data(), settings = settings(), setting_cat_val = "measure", - charts=input$charts, + charts=charts, ns=ns ) ) }) output$appearance_settings_ui <- renderUI({ - req(input$charts) + charts <- isolate(input$charts) + req(charts) tagList( createSettingsUI( data=data(), settings = settings(), setting_cat_val = "appearance", - charts=input$charts, + charts=charts, ns=ns ) ) }) + ######### Hide Settings that are not relevant to selected charts ######## + observeEvent(input$charts,{ + + #Make sure all settings are showing before you start subsetting! (alternatively could show those missing) + for (setting in input_names()) { + shinyjs::show(id=paste0("ctl_",setting)) + } + # 1. get all possible metadata (input_names always reflects the current chart selections and is already filtered) + # so I'm grabbing all of these options so I cnan determine which should be hidden + metadata <- getSettingsMetadata( + cols=c("text_key") + ) + + # 2. identify which settings in input_names() are not relevant + settings_to_drop <- setdiff(metadata,input_names()) + + # 3. use shinyJS::hide() to hide these inputs (loop thru) + for (setting in settings_to_drop) { + shinyjs::hide(id=paste0("ctl_",setting)) + } + + }) + outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE) @@ -108,7 +134,7 @@ renderSettings <- function(input, output, session, data, settings, status){ # # update field-level inputs if a column level setting changes # dependent on change in data, chart selection, or column-level input - ###################################################################### + ######################3################################################ observe({ field_rows <- getSettingsMetadata( From 9e6e98b259ebe27a3a8970a92133ac83851d54d9 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 15 Apr 2019 14:13:55 -0400 Subject: [PATCH 68/98] clean up --- .../modules/renderSettings/renderSettings.R | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index de247792..d796e6ce 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -105,20 +105,21 @@ renderSettings <- function(input, output, session, data, settings, status){ ######### Hide Settings that are not relevant to selected charts ######## observeEvent(input$charts,{ - #Make sure all settings are showing before you start subsetting! (alternatively could show those missing) + # Make sure all relevant settings are showing for (setting in input_names()) { shinyjs::show(id=paste0("ctl_",setting)) } - # 1. get all possible metadata (input_names always reflects the current chart selections and is already filtered) - # so I'm grabbing all of these options so I cnan determine which should be hidden - metadata <- getSettingsMetadata( + + # Get all possible metadata (input_names always reflects the current chart selections and is already filtered) + # so I'm grabbing all of these options so I can determine which should be hidden + all_settings <- getSettingsMetadata( cols=c("text_key") ) - # 2. identify which settings in input_names() are not relevant - settings_to_drop <- setdiff(metadata,input_names()) + # Identify which settings in input_names() are not relevant + settings_to_drop <- setdiff(all_settings,input_names()) - # 3. use shinyJS::hide() to hide these inputs (loop thru) + # Use shinyJS::hide() to hide these inputs for (setting in settings_to_drop) { shinyjs::hide(id=paste0("ctl_",setting)) } @@ -134,7 +135,7 @@ renderSettings <- function(input, output, session, data, settings, status){ # # update field-level inputs if a column level setting changes # dependent on change in data, chart selection, or column-level input - ######################3################################################ + ###################################################################### observe({ field_rows <- getSettingsMetadata( From e05d3cbd568e159ef866b14dc6bf159f62de626d Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Wed, 17 Apr 2019 15:00:39 -0400 Subject: [PATCH 69/98] handle case where no charts are select, fix #235 --- .../modules/renderSettings/renderSettings.R | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index d796e6ce..ac4b3142 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -50,8 +50,15 @@ renderSettings <- function(input, output, session, data, settings, status){ ns <- session$ns #List of all inputs - input_names <- reactive({safetyGraphics:::getSettingsMetadata(charts=input$charts, cols="text_key")}) - + # Null if no charts are selected + input_names <- reactive({ + if(!is.null(input$charts)){ + safetyGraphics:::getSettingsMetadata(charts=input$charts, cols="text_key") + } else{ + NULL + } + }) + ###################################################################### # create settings UI # - chart selection -> gather all necessary UI elements @@ -104,27 +111,31 @@ renderSettings <- function(input, output, session, data, settings, status){ ######### Hide Settings that are not relevant to selected charts ######## observeEvent(input$charts,{ - + + input_names <- isolate(input_names()) + # Make sure all relevant settings are showing - for (setting in input_names()) { - shinyjs::show(id=paste0("ctl_",setting)) + if (!is.null(input_names)){ + for (setting in input_names) { + shinyjs::show(id=paste0("ctl_",setting)) + } } - + # Get all possible metadata (input_names always reflects the current chart selections and is already filtered) # so I'm grabbing all of these options so I can determine which should be hidden all_settings <- getSettingsMetadata( cols=c("text_key") ) - + # Identify which settings in input_names() are not relevant - settings_to_drop <- setdiff(all_settings,input_names()) - + settings_to_drop <- setdiff(all_settings,input_names) + # Use shinyJS::hide() to hide these inputs for (setting in settings_to_drop) { shinyjs::hide(id=paste0("ctl_",setting)) } - - }) + + }, ignoreNULL=FALSE) ## input$charts = NULL if none are selected outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) From 90cb6b3282f5477c4ec9b65290fb45b8cbaa9a57 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Thu, 18 Apr 2019 11:51:05 -0700 Subject: [PATCH 70/98] css updates --- .../modules/renderSettings/renderSettings.R | 8 ++-- inst/eDISH_app/server.R | 42 ++++++++++++------- inst/eDISH_app/www/index.css | 30 ++++++++++--- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index ac4b3142..6189ce7d 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -58,7 +58,7 @@ renderSettings <- function(input, output, session, data, settings, status){ NULL } }) - + ###################################################################### # create settings UI # - chart selection -> gather all necessary UI elements @@ -118,7 +118,7 @@ renderSettings <- function(input, output, session, data, settings, status){ if (!is.null(input_names)){ for (setting in input_names) { shinyjs::show(id=paste0("ctl_",setting)) - } + } } # Get all possible metadata (input_names always reflects the current chart selections and is already filtered) @@ -136,7 +136,7 @@ renderSettings <- function(input, output, session, data, settings, status){ } }, ignoreNULL=FALSE) ## input$charts = NULL if none are selected - + outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE) @@ -268,7 +268,7 @@ renderSettings <- function(input, output, session, data, settings, status){ unique %>% group_by(text_key) %>% mutate(num_fail = sum(valid==FALSE)) %>% - mutate(icon = ifelse(num_fail==0, "",""))%>% + mutate(icon = ifelse(num_fail==0, "",""))%>% mutate( message_long = paste(message, collapse = " ") %>% trimws(), message_short = case_when( diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index dd32bae8..18a5c1b9 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -2,11 +2,11 @@ # - calls dataUpload module (data tab) # - calls renderSettings module (settings tab) # - calls chart modules (chart tab) -# - uses render UI to append a red X or green check on tab title, +# - 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){ - + ############################################################## # initialize dataUpload module # @@ -25,9 +25,9 @@ function(input, output, session){ # # returns updated settings and validation status ############################################################## - + settings_new <- callModule( - renderSettings, + renderSettings, "settingsUI", data = reactive(dataUpload_out$data_selected()), settings = reactive(dataUpload_out$settings()), @@ -35,17 +35,30 @@ settings_new <- callModule( ) +# toggle css class of chart tabs +observeEvent(settings_new$status(),{ + + for (chart in settings_new$charts()){ + valid <- settings_new$status()[[chart]]$valid + + ## code to toggle css for chart-specific tab here + # we will need to deal with the fact that the tabs don't have IDs :) + # toggleClass(class=?, id=chart_tab_id, "valid", chart_status=="valid") + # toggleClass(class=?, id=chart_tab_id, "invalid", chart_status=="invalid") + } +}) + ############################################################## # Initialize Charts Modules ############################################################## - + # set up all chart tabs from the start (allcharts defined in global.R) # generated from server.R so we can do this dynamically in future.. for (chart in all_charts){ - + tabfun <- match.fun(paste0("render_", chart, "_chartUI")) # module UI for given tab tabid <- paste0(chart, "_tab_title") - + appendTab( inputId = "nav_id", tab = tabPanel( @@ -62,21 +75,21 @@ settings_new <- callModule( unselected_charts <- all_charts[!all_charts %in% selected_charts] for(chart in unselected_charts){ - hideTab(inputId = "nav_id", target = chart) + hideTab(inputId = "nav_id", target = chart) } for(chart in selected_charts){ showTab(inputId = "nav_id", target = chart) } }) - + # call all chart modules # - # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and + # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and # the value for "valid" doesn't always get passed directly. # Moving to renderChart module will hopefully help here for (chart in all_charts){ - + for (chart in all_charts){ - + modfun <- match.fun(paste0("render_", chart, "_chart")) callModule( module = modfun, @@ -85,9 +98,8 @@ settings_new <- callModule( settings = reactive(settings_new$settings()), valid = reactive(settings_new$status()[[chart]]$valid) ) - } - - + + session$onSessionEnded(stopApp) } diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css index 2620f204..9b1d6240 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -19,8 +19,8 @@ } .control-wrap .select-wrap .form-group{ - width:90%; /* TODO: don't love this ... update eventually */ - display:inline-block; + width:90%; /* TODO: don't love this ... update eventually */ + display:inline-block; margin-bottom:0; } @@ -40,7 +40,25 @@ } -/* Validation Coloring */ + +/* chart validation formatting */ +#nav_id li.dropdown ul li a.valid:after{ + font-family: "Font Awesome 5 Free"; + font-weight: 900; + content: "\f00c"; + color: green; + padding-left:0.3em; +} + +#nav_id li.dropdown ul li a.invalid:after{ + font-family: "Font Awesome 5 Free"; + font-weight: 900; + content: "\f00d"; + color: red; + padding-left:0.3em; +} + +/* Setting Validation Coloring */ .control-wrap.valid .select-wrap .status{ color:green; @@ -61,7 +79,7 @@ /* Settings - header tweaks */ .section h3 { margin:0; -} +} .section h3 .form-group { display:inline; @@ -72,10 +90,10 @@ } -.ok { +.ok { color:#008000; } .notok { color: #FF0000; -} \ No newline at end of file +} From 1c2a3d698689ac462da572611d844a2eb5f61f1e Mon Sep 17 00:00:00 2001 From: jwildfire Date: Thu, 18 Apr 2019 13:53:03 -0700 Subject: [PATCH 71/98] refactor chart control. fix #233 --- .../modules/renderSettings/renderSettings.R | 29 ++++++++++++++----- .../modules/renderSettings/renderSettingsUI.R | 25 ++++------------ inst/eDISH_app/www/index.css | 20 +++++-------- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index ac4b3142..2ebb28e0 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -49,16 +49,34 @@ renderSettings <- function(input, output, session, data, settings, status){ ns <- session$ns + output$charts_wrap_ui <- renderUI({ + checkboxGroupButtons( + ns("charts"), + label = NULL, + choices = c( + "e-DISH" = "edish", + "Safety Histogram" = "safetyhistogram" + ), + selected=c("edish", "safetyhistogram"), + checkIcon = list( + yes = icon("ok", lib = "glyphicon"), + no = icon("remove",lib = "glyphicon") + ), + status="primary" + ) + }) + #List of all inputs # Null if no charts are selected input_names <- reactive({ + print(input$charts) if(!is.null(input$charts)){ safetyGraphics:::getSettingsMetadata(charts=input$charts, cols="text_key") } else{ NULL } - }) - + }) + ###################################################################### # create settings UI # - chart selection -> gather all necessary UI elements @@ -68,7 +86,6 @@ renderSettings <- function(input, output, session, data, settings, status){ output$data_mapping_ui <- renderUI({ charts <- isolate(input$charts) - req(charts) tagList( createSettingsUI( data=data(), @@ -83,7 +100,6 @@ renderSettings <- function(input, output, session, data, settings, status){ output$measure_settings_ui <- renderUI({ charts <- isolate(input$charts) - req(charts) tagList( createSettingsUI( data=data(), @@ -97,7 +113,6 @@ renderSettings <- function(input, output, session, data, settings, status){ output$appearance_settings_ui <- renderUI({ charts <- isolate(input$charts) - req(charts) tagList( createSettingsUI( data=data(), @@ -118,7 +133,7 @@ renderSettings <- function(input, output, session, data, settings, status){ if (!is.null(input_names)){ for (setting in input_names) { shinyjs::show(id=paste0("ctl_",setting)) - } + } } # Get all possible metadata (input_names always reflects the current chart selections and is already filtered) @@ -136,7 +151,7 @@ renderSettings <- function(input, output, session, data, settings, status){ } }, ignoreNULL=FALSE) ## input$charts = NULL if none are selected - + outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R b/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R index b2ee8589..4e1d1eef 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettingsUI.R @@ -1,24 +1,11 @@ renderSettingsUI <- function(id){ ns <- NS(id) - tagList( - fluidRow( - column(12, - class="chartSelect section", - checkboxGroupInput( - ns("charts"), - "Select Chart(s):", - choices = c("e-DISH" = "edish", - "Safety Histogram" = "safetyhistogram"), - selected=c("edish", "safetyhistogram") - ) - ) - ), - #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) - ) + #TODO - make this a loop based on metadata + fluidRow( + createSettingsSection("charts_wrap", "Charts",12,ns), + 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/www/index.css b/inst/eDISH_app/www/index.css index 2620f204..e0864175 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -1,9 +1,3 @@ -/* --- hide the chartSelect div until we're ready to implement multiple charts --- */ -/*.chartSelect{ - display:none; -} -/* ------------------------------------------------------------------------------- */ - .section{ min-width:400px; } @@ -19,8 +13,8 @@ } .control-wrap .select-wrap .form-group{ - width:90%; /* TODO: don't love this ... update eventually */ - display:inline-block; + width:90%; /* TODO: don't love this ... update eventually */ + display:inline-block; margin-bottom:0; } @@ -39,7 +33,9 @@ cursor:help; } - +#settingsUI-charts_wrap_ui{ + margin-top:15px; +} /* Validation Coloring */ .control-wrap.valid .select-wrap .status{ @@ -61,7 +57,7 @@ /* Settings - header tweaks */ .section h3 { margin:0; -} +} .section h3 .form-group { display:inline; @@ -72,10 +68,10 @@ } -.ok { +.ok { color:#008000; } .notok { color: #FF0000; -} \ No newline at end of file +} From 35cda99eb4f6028edf93b79a9b726261c438d477 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 19 Apr 2019 06:03:02 -0400 Subject: [PATCH 72/98] hardcode callModules --- inst/eDISH_app/server.R | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index dd32bae8..c26b18c7 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -68,25 +68,39 @@ settings_new <- callModule( showTab(inputId = "nav_id", target = chart) } }) - + + # call all chart modules # - # I'm thinking this code set up (loop + callModule() using reactives) isn't ideal and - # the value for "valid" doesn't always get passed directly. - # Moving to renderChart module will hopefully help here for (chart in all_charts){ + # loop is broken so going back to hardcode for now. + # this will change in the future anyway - for (chart in all_charts){ - - modfun <- match.fun(paste0("render_", chart, "_chart")) + # for (chart in all_charts){ + # + # modfun <- match.fun(paste0("render_", chart, "_chart")) + # callModule( + # module = modfun, + # id = paste0("chart", chart), + # data = reactive(dataUpload_out$data_selected()), + # settings = reactive(settings_new$settings()), + # valid = reactive(settings_new$status()[[chart]]$valid) + # ) + # + # } callModule( - module = modfun, - id = paste0("chart", chart), + module = render_edish_chart, + id = paste0("chart", "edish"), data = reactive(dataUpload_out$data_selected()), settings = reactive(settings_new$settings()), - valid = reactive(settings_new$status()[[chart]]$valid) + valid = reactive(settings_new$status()[["edish"]]$valid) + ) + callModule( + module = render_safetyhistogram_chart, + id = paste0("chart", "safetyhistogram"), + data = reactive(dataUpload_out$data_selected()), + settings = reactive(settings_new$settings()), + valid = reactive(settings_new$status()[["safetyhistogram"]]$valid) ) - - } session$onSessionEnded(stopApp) From 9814b7b51307d68b0837cda9c6590cef638461ef Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 19 Apr 2019 06:04:36 -0400 Subject: [PATCH 73/98] Change export chart label --- inst/eDISH_app/modules/renderChart/render_edish_chart.R | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R index d0aba02a..8c5aa75c 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -23,12 +23,9 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ req(data()) req(settings()) - # if (valid()==TRUE){ trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) eDISH(data = trimmed_data, settings = settings()) - # } else{ - # return() - # } + }) @@ -44,7 +41,7 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ style="float: right;", span(class = "navbar-brand", #using one of the default nav bar classes to get css close style="padding: 8px;", #then little tweak to ensure vertical alignment - downloadButton(ns("reportDL"), "Export Chart")) ) + downloadButton(ns("reportDL"), "Export eDISH Chart")) ) ) } else { From faa07251f25fdfa29fa35ab7ae570e876d1b2425 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 19 Apr 2019 06:04:45 -0400 Subject: [PATCH 74/98] remove comment --- .../modules/renderChart/render_safetyhistogram_chart.R | 6 ------ 1 file changed, 6 deletions(-) diff --git a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R index 1f577343..b5b9c6a2 100644 --- a/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_safetyhistogram_chart.R @@ -16,18 +16,12 @@ render_safetyhistogram_chart <- function(input, output, session, data, settings, ns <- session$ns - # render eDISH chart if settings pass validation output$chart <- renderSafetyHistogram({ req(data()) req(settings()) - # if (valid()==TRUE){ - #trimmed_data <- safetyGraphics:::trimData(data = data(), settings = settings()) safetyHistogram(data = data(), settings = settings()) - # } else{ - # return() - # } }) } From 6d8269426ebcc40785676b98fd67c67339f35cb5 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 19 Apr 2019 13:15:23 -0400 Subject: [PATCH 75/98] add rowwise --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index ac4b3142..81e281a8 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -221,8 +221,10 @@ renderSettings <- function(input, output, session, data, settings, status){ keys <- input_names() values<- keys %>% map(~getValues(.x)) - inputDF <- tibble(text_key=keys, customValue=values) %>% + inputDF <- tibble(text_key=keys, customValue=values)%>% + rowwise %>% filter(!is.null(customValue[[1]])) + if(nrow(inputDF)>0){ settings <- generateSettings(custom_settings=inputDF, charts=input$charts) }else{ From 26733b7abc522f517642cd765e37f35e29606c4e Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Fri, 19 Apr 2019 15:18:23 -0400 Subject: [PATCH 76/98] add css for valid/invalid --- inst/eDISH_app/www/index.css | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css index 9b1d6240..b897eae8 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -58,6 +58,24 @@ padding-left:0.3em; } +/* chart validation formatting */ +.valid { + font-family: "Font Awesome 5 Free"; + font-weight: 900; + content: "\f00c"; + color: green; + padding-left:0.3em; +} + +.invalid { + font-family: "Font Awesome 5 Free"; + font-weight: 900; + content: "\f00c"; + color: red; + padding-left:0.3em; +} + + /* Setting Validation Coloring */ .control-wrap.valid .select-wrap .status{ From f1fc4b0824c02a389d97a2deec5925673bdf9085 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 22 Apr 2019 08:50:26 -0400 Subject: [PATCH 77/98] insert span on chart tabs for class toggling --- inst/eDISH_app/server.R | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 18a5c1b9..93f9ce35 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -35,16 +35,19 @@ settings_new <- callModule( ) -# toggle css class of chart tabs -observeEvent(settings_new$status(),{ - +#toggle css class of chart tabs +observeEvent(settings_new$status(),{ for (chart in settings_new$charts()){ valid <- settings_new$status()[[chart]]$valid - + ## code to toggle css for chart-specific tab here # we will need to deal with the fact that the tabs don't have IDs :) # toggleClass(class=?, id=chart_tab_id, "valid", chart_status=="valid") # toggleClass(class=?, id=chart_tab_id, "invalid", chart_status=="invalid") + + toggleClass(id =paste0("tabid_",chart), class="validchart", condition=valid==TRUE) + toggleClass(id =paste0("tabid_",chart), class="invalidchart", condition=valid==FALSE) + # addClass(selector= "#nav_id li.dropdown ul li a[data-value='edish’]", class="validchart") #, condition=valid==TRUE) } }) @@ -62,7 +65,8 @@ observeEvent(settings_new$status(),{ appendTab( inputId = "nav_id", tab = tabPanel( - title = chart, + title = #chart, + span(id = paste0("tabid_", chart), class = "validchart", chart), tabfun(paste0("chart", chart)) ), menuName = "Charts" @@ -75,10 +79,12 @@ observeEvent(settings_new$status(),{ unselected_charts <- all_charts[!all_charts %in% selected_charts] for(chart in unselected_charts){ - hideTab(inputId = "nav_id", target = chart) + hideTab(inputId = "nav_id", + target = paste0('', chart, '')) } for(chart in selected_charts){ - showTab(inputId = "nav_id", target = chart) + showTab(inputId = "nav_id", + target = paste0('', chart, '')) } }) From 64b87ce5cf54f6b3465d4e0f58d874f91f90d0ea Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 22 Apr 2019 09:24:01 -0400 Subject: [PATCH 78/98] figured out the selector --- inst/eDISH_app/server.R | 16 +++++----------- inst/eDISH_app/www/index.css | 21 ++------------------- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index 93f9ce35..cc5e4922 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -41,13 +41,8 @@ observeEvent(settings_new$status(),{ valid <- settings_new$status()[[chart]]$valid ## code to toggle css for chart-specific tab here - # we will need to deal with the fact that the tabs don't have IDs :) - # toggleClass(class=?, id=chart_tab_id, "valid", chart_status=="valid") - # toggleClass(class=?, id=chart_tab_id, "invalid", chart_status=="invalid") - - toggleClass(id =paste0("tabid_",chart), class="validchart", condition=valid==TRUE) - toggleClass(id =paste0("tabid_",chart), class="invalidchart", condition=valid==FALSE) - # addClass(selector= "#nav_id li.dropdown ul li a[data-value='edish’]", class="validchart") #, condition=valid==TRUE) + toggleClass(selector= paste0("#nav_id li a[data-value='", chart, "']"), class="valid", condition=valid==TRUE) + toggleClass(selector= paste0("#nav_id li a[data-value='", chart, "']"), class="invalid", condition=valid==FALSE) } }) @@ -65,8 +60,7 @@ observeEvent(settings_new$status(),{ appendTab( inputId = "nav_id", tab = tabPanel( - title = #chart, - span(id = paste0("tabid_", chart), class = "validchart", chart), + title = chart, tabfun(paste0("chart", chart)) ), menuName = "Charts" @@ -80,11 +74,11 @@ observeEvent(settings_new$status(),{ for(chart in unselected_charts){ hideTab(inputId = "nav_id", - target = paste0('', chart, '')) + target = chart) } for(chart in selected_charts){ showTab(inputId = "nav_id", - target = paste0('', chart, '')) + target = chart) } }) diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css index b897eae8..7b733743 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -42,7 +42,7 @@ /* chart validation formatting */ -#nav_id li.dropdown ul li a.valid:after{ +#nav_id li a.valid:after{ font-family: "Font Awesome 5 Free"; font-weight: 900; content: "\f00c"; @@ -50,7 +50,7 @@ padding-left:0.3em; } -#nav_id li.dropdown ul li a.invalid:after{ +#nav_id li a.invalid:after{ font-family: "Font Awesome 5 Free"; font-weight: 900; content: "\f00d"; @@ -58,23 +58,6 @@ padding-left:0.3em; } -/* chart validation formatting */ -.valid { - font-family: "Font Awesome 5 Free"; - font-weight: 900; - content: "\f00c"; - color: green; - padding-left:0.3em; -} - -.invalid { - font-family: "Font Awesome 5 Free"; - font-weight: 900; - content: "\f00c"; - color: red; - padding-left:0.3em; -} - /* Setting Validation Coloring */ From 19139b61bc2342486e136ade75ae7ed978ade851 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 22 Apr 2019 09:43:22 -0400 Subject: [PATCH 79/98] more specific selector --- inst/eDISH_app/server.R | 4 ++-- inst/eDISH_app/www/index.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/inst/eDISH_app/server.R b/inst/eDISH_app/server.R index cc5e4922..4d420724 100644 --- a/inst/eDISH_app/server.R +++ b/inst/eDISH_app/server.R @@ -41,8 +41,8 @@ observeEvent(settings_new$status(),{ valid <- settings_new$status()[[chart]]$valid ## code to toggle css for chart-specific tab here - toggleClass(selector= paste0("#nav_id li a[data-value='", chart, "']"), class="valid", condition=valid==TRUE) - toggleClass(selector= paste0("#nav_id li a[data-value='", chart, "']"), class="invalid", condition=valid==FALSE) + toggleClass(selector= paste0("#nav_id li.dropdown ul.dropdown-menu li a[data-value='", chart, "']"), class="valid", condition=valid==TRUE) + toggleClass(selector= paste0("#nav_id li.dropdown ul.dropdown-menu li a[data-value='", chart, "']"), class="invalid", condition=valid==FALSE) } }) diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css index 7b733743..06934e0e 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -42,7 +42,7 @@ /* chart validation formatting */ -#nav_id li a.valid:after{ +#nav_id li.dropdown ul.dropdown-menu li a.valid:after{ font-family: "Font Awesome 5 Free"; font-weight: 900; content: "\f00c"; @@ -50,7 +50,7 @@ padding-left:0.3em; } -#nav_id li a.invalid:after{ +#nav_id li.dropdown ul.dropdown-menu li a.invalid:after{ font-family: "Font Awesome 5 Free"; font-weight: 900; content: "\f00d"; From f043d2227acfb294c65ac61bb02f2fe07effb5d0 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Mon, 22 Apr 2019 07:15:08 -0700 Subject: [PATCH 80/98] fix stray merge issue --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index f42fb720..89ebc12c 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -75,11 +75,9 @@ renderSettings <- function(input, output, session, data, settings, status){ } else{ NULL } -<<<<<<< HEAD + }) -======= - }) ->>>>>>> dev-v0.10.0 + ###################################################################### # create settings UI From c2e79f4b0df0500ad3680940aa97ac8d732dc5aa Mon Sep 17 00:00:00 2001 From: jwildfire Date: Mon, 22 Apr 2019 07:29:26 -0700 Subject: [PATCH 81/98] allow null in hasField/Column checks. fix #240 --- R/hasColumn.R | 11 ++++++++--- R/hasField.R | 22 +++++++++++++--------- tests/testthat/test_hasColumn.R | 5 ++++- tests/testthat/test_hasField.R | 3 +++ 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/R/hasColumn.R b/R/hasColumn.R index d64fc52d..b1149dc1 100644 --- a/R/hasColumn.R +++ b/R/hasColumn.R @@ -14,10 +14,15 @@ hasColumn <- function(columnName, data){ stopifnot( - typeof(columnName)=="character", - length(columnName)==1, + typeof(columnName)=="character" || is.null(columnName), + length(columnName)==1 || is.null(columnName), is.data.frame(data) ) - return(toupper(columnName) %in% toupper(colnames(data))) + if(is.null(columnName)){ + return(FALSE) + } else { + return(toupper(columnName) %in% toupper(colnames(data))) + } + } diff --git a/R/hasField.R b/R/hasField.R index ab038568..f4612c6a 100644 --- a/R/hasField.R +++ b/R/hasField.R @@ -16,18 +16,22 @@ hasField<- function(fieldValue, columnName, data){ stopifnot( length(fieldValue)==1, - typeof(columnName)=="character", - length(columnName)==1, + typeof(columnName)=="character" || is.null(columnName), + length(columnName)==1 || is.null(columnName), is.data.frame(data) ) - columnFound <- hasColumn(columnName=columnName, data=data) - if(columnFound){ + if(is.null(columnName)){ + return(FALSE) + } else { + columnFound <- hasColumn(columnName=columnName, data=data) + if(columnFound){ + validFields <- unique(data[[columnName]]) + } else{ + validFields <- c() + } + validFields <- unique(data[[columnName]]) - } else{ - validFields <- c() + return(fieldValue %in% validFields) } - - validFields <- unique(data[[columnName]]) - return(fieldValue %in% validFields) } diff --git a/tests/testthat/test_hasColumn.R b/tests/testthat/test_hasColumn.R index 5145492b..4751ebc8 100644 --- a/tests/testthat/test_hasColumn.R +++ b/tests/testthat/test_hasColumn.R @@ -13,7 +13,10 @@ test_that("columns are found when expected",{ # returns false when fieldValue isn't there or there is a type mismatch expect_false(hasColumn(columnName="PARAMETER",data=adlbc)) expect_false(hasColumn(columnName="SUBJID2",data=adlbc)) - + + # returns false for null columnName + expect_false(hasColumn(columnName=NULL,data=adlbc)) + # fails with invalid parameters expect_error(hasColumn(columnName=123,data=adlbc)) expect_error(hasColumn(columnName=c("PARAM","SUBJID"),data=adlbc)) diff --git a/tests/testthat/test_hasField.R b/tests/testthat/test_hasField.R index a1e9197e..7c18ed53 100644 --- a/tests/testthat/test_hasField.R +++ b/tests/testthat/test_hasField.R @@ -20,6 +20,9 @@ test_that("fields are found when expected",{ expect_false(hasField(fieldValue="Not_a_real_value",columnName="PARAM",data=adlbc)) expect_false(hasField(fieldValue=12,columnName="PARAM",data=adlbc)) + # returns false for null columnName + expect_false(hasField(fieldValue="Bilirubin (umol/L)",columnName=NULL,data=adlbc)) + # fails with invalid parameters expect_error(hasField(fieldValue="Bilirubin (umol/L)",columnName=c("PARAM","ID"),data=adlbc)) expect_error(hasField(columnName="PARAM",data=list(adlbc))) #fieldValue missing From 65a32c671c76be73f346b3d8827b901d8890b072 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 22 Apr 2019 12:55:18 -0400 Subject: [PATCH 82/98] reference status_df() outside of for loop --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 89ebc12c..9ff7693a 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -49,6 +49,7 @@ renderSettings <- function(input, output, session, data, settings, status){ ns <- session$ns + output$charts_wrap_ui <- renderUI({ checkboxGroupButtons( ns("charts"), @@ -69,7 +70,6 @@ renderSettings <- function(input, output, session, data, settings, status){ #List of all inputs # Null if no charts are selected input_names <- reactive({ - print(input$charts) if(!is.null(input$charts)){ safetyGraphics:::getSettingsMetadata(charts=input$charts, cols="text_key") } else{ @@ -306,8 +306,8 @@ renderSettings <- function(input, output, session, data, settings, status){ ###################################################################### # print validation messages ###################################################################### - observe({ - for (key in isolate(input_names())){ + observeEvent(status_df(), { + 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"] From d68b70632266aa38384f59886e7076fd8546cc17 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Mon, 22 Apr 2019 13:56:29 -0400 Subject: [PATCH 83/98] remove non-ASCII string --- data-raw/settingsMetadata.csv | 2 +- data/settingsMetadata.rda | Bin 2060 -> 2051 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/data-raw/settingsMetadata.csv b/data-raw/settingsMetadata.csv index 4d1d7f86..21feb7d2 100644 --- a/data-raw/settingsMetadata.csv +++ b/data-raw/settingsMetadata.csv @@ -25,6 +25,6 @@ TRUE,FALSE,r_ratio_cut,Default R Ratio Cut,Default cut point for R Ratio filter. TRUE,FALSE,showTitle,Show Chart Title? ,Specifies whether the title should be drawn above the controls.,logical,FALSE,FALSE,NA,FALSE,,appearance TRUE,FALSE,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 FALSE,TRUE,unit_col,Unit column,Unit of measure variable name,character,TRUE,TRUE,character,FALSE,,data -FALSE,TRUE,start_value,Measure start value,Value of variable defined in measure_col to be rendered in the histogram when the widget loads,character,FALSE,FALSE,NA,TRUE,measure_col,data +FALSE,TRUE,start_value,Measure start value,Value of variable defined in measure_col to be rendered in the histogram when the widget loads,character,FALSE,FALSE,NA,TRUE,measure_col,data FALSE,TRUE,details,Details columns,"An optional list of specifications for details listing. Each column to be added to details listing is a nested, named list (containing the variable name: ""value_col"" and associated label: ""label"") within the larger list.",vector,FALSE,TRUE,NA,FALSE,,data FALSE,TRUE,missingValues,Missing values,Values defining a missing value in the selected 'value' column,vector,FALSE,FALSE,NA,FALSE,,data diff --git a/data/settingsMetadata.rda b/data/settingsMetadata.rda index 27f1bf73b0423c543f924dab7fd1f2b05773ce33..edbbc4b4aed593b64d69266092e36a8b6b88be61 100644 GIT binary patch literal 2051 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 Date: Mon, 22 Apr 2019 13:56:46 -0400 Subject: [PATCH 84/98] tweak tests --- tests/testthat/test_evaluateStandard.R | 2 +- tests/testthat/test_getRequiredSettings.R | 8 ++++---- tests/testthat/test_getSettingsMetadata.R | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test_evaluateStandard.R b/tests/testthat/test_evaluateStandard.R index 7262d10e..f8c704b9 100644 --- a/tests/testthat/test_evaluateStandard.R +++ b/tests/testthat/test_evaluateStandard.R @@ -29,7 +29,7 @@ test_that("expected number of checks (in)valid",{ expect_equal(a[["valid_count"]],2) expect_equal(a[["invalid_count"]],9) expect_equal(a[["total_count"]],11) - expect_equal(a[["match_percent"]],.182) + expect_equal(round(a[["match_percent"]],3), .182) expect_true(a[["checks"]]%>%filter(text_key=="measure_col")%>%select(valid)%>%unlist) }) diff --git a/tests/testthat/test_getRequiredSettings.R b/tests/testthat/test_getRequiredSettings.R index ea3fed4a..b1d463cc 100644 --- a/tests/testthat/test_getRequiredSettings.R +++ b/tests/testthat/test_getRequiredSettings.R @@ -4,15 +4,15 @@ library(testthat) defaultRequiredSettings <- list( list("id_col"), + list("value_col"), list("measure_col"), - list("measure_values","ALP"), list("measure_values","ALT"), list("measure_values","AST"), list("measure_values","TB"), - list("normal_col_high"), + list("measure_values","ALP"), list("normal_col_low"), - list("studyday_col"), - list("value_col") + list("normal_col_high"), + list("studyday_col") ) diff --git a/tests/testthat/test_getSettingsMetadata.R b/tests/testthat/test_getSettingsMetadata.R index b88f5e1c..ba7f5584 100644 --- a/tests/testthat/test_getSettingsMetadata.R +++ b/tests/testthat/test_getSettingsMetadata.R @@ -20,7 +20,7 @@ customMetadata<- data.frame( mergedMetadata = suppressWarnings(bind_rows( rawMetadata%>%mutate(chart_linechart= FALSE)%>%mutate(chart_barchart= FALSE), - customMetadata%>%mutate(chart_edish= FALSE) + customMetadata%>%mutate(chart_edish= FALSE, chart_safetyhistogram=FALSE) )) @@ -72,7 +72,7 @@ test_that("charts parameter works as expected",{ linesandbars <- safetyGraphics:::getSettingsMetadata(charts=c("linechart","barchart"),metadata=mergedMetadata) expect_equal(dim(linesandbars)[1],2) - allcharts <- safetyGraphics:::getSettingsMetadata(charts=c("linechart","barchart","edish"),metadata=mergedMetadata) + allcharts <- safetyGraphics:::getSettingsMetadata(charts=c("linechart","barchart","edish","safetyhistogram"),metadata=mergedMetadata) expect_equal(dim(allcharts)[1],dim(mergedMetadata)[1]) }) From 71f6370f52e24c43bd1ec00c52f5cf4654a2f12d Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 22 Apr 2019 13:56:51 -0400 Subject: [PATCH 85/98] update safetyHistogram to v2.2.3 --- .../safetyHistogram.js | 10 ++++++++++ inst/htmlwidgets/safetyHistogram.yaml | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) rename inst/htmlwidgets/lib/{safety-histogram-2.2.2 => safety-histogram-2.2.3}/safetyHistogram.js (98%) diff --git a/inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js b/inst/htmlwidgets/lib/safety-histogram-2.2.3/safetyHistogram.js similarity index 98% rename from inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js rename to inst/htmlwidgets/lib/safety-histogram-2.2.3/safetyHistogram.js index d1b2727d..5fff2830 100644 --- a/inst/htmlwidgets/lib/safety-histogram-2.2.2/safetyHistogram.js +++ b/inst/htmlwidgets/lib/safety-histogram-2.2.3/safetyHistogram.js @@ -196,6 +196,11 @@ //Define default details. var defaultDetails = [{ value_col: settings.id_col, label: 'Subject Identifier' }]; + + if (!(settings.filters instanceof Array)) { + settings.filters = typeof settings.filters == 'string' ? [settings.filters] : []; + } + if (settings.filters) settings.filters.forEach(function(filter) { return defaultDetails.push({ @@ -219,6 +224,11 @@ label: 'Upper Limit of Normal' }); + //If [settings.details] is not an array: + if (!(settings.details instanceof Array)) { + settings.details = typeof settings.details == 'string' ? [settings.details] : []; + } + //If [settings.details] is not specified: if (!settings.details) settings.details = defaultDetails; else { diff --git a/inst/htmlwidgets/safetyHistogram.yaml b/inst/htmlwidgets/safetyHistogram.yaml index 2f678ba6..4277978b 100644 --- a/inst/htmlwidgets/safetyHistogram.yaml +++ b/inst/htmlwidgets/safetyHistogram.yaml @@ -9,7 +9,7 @@ dependencies: script: webcharts.js stylesheet: webcharts.css - name: safety-histogram - version: 2.2.2 - src: htmlwidgets/lib/safety-histogram-2.2.2 + version: 2.2.3 + src: htmlwidgets/lib/safety-histogram-2.2.3 script: safetyHistogram.js From 9dd254deb37d019280b5235bd64cc4ef2506ba7d Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 22 Apr 2019 14:14:55 -0400 Subject: [PATCH 86/98] update eDish to v0.16.5 --- inst/htmlwidgets/eDISH.yaml | 4 ++-- .../safetyedish.js | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) rename inst/htmlwidgets/lib/{safety-eDISH-0.16.3 => safety-eDISH-0.16.5}/safetyedish.js (99%) diff --git a/inst/htmlwidgets/eDISH.yaml b/inst/htmlwidgets/eDISH.yaml index 17ad864a..3faf2d99 100644 --- a/inst/htmlwidgets/eDISH.yaml +++ b/inst/htmlwidgets/eDISH.yaml @@ -9,6 +9,6 @@ dependencies: script: webcharts.js stylesheet: webcharts.css - name: safety-eDish - version: 0.16.3 - src: htmlwidgets/lib/safety-eDISH-0.16.3 + version: 0.16.5 + src: htmlwidgets/lib/safety-eDISH-0.16.5 script: safetyedish.js diff --git a/inst/htmlwidgets/lib/safety-eDISH-0.16.3/safetyedish.js b/inst/htmlwidgets/lib/safety-eDISH-0.16.5/safetyedish.js similarity index 99% rename from inst/htmlwidgets/lib/safety-eDISH-0.16.3/safetyedish.js rename to inst/htmlwidgets/lib/safety-eDISH-0.16.5/safetyedish.js index 03550241..b70faecf 100644 --- a/inst/htmlwidgets/lib/safety-eDISH-0.16.3/safetyedish.js +++ b/inst/htmlwidgets/lib/safety-eDISH-0.16.5/safetyedish.js @@ -395,7 +395,7 @@ //make sure filters is an Array if (!(settings.filters instanceof Array)) { - settings.filters = []; + settings.filters = typeof settings.filters == 'string' ? [settings.filters] : []; } //Define default details. @@ -473,15 +473,29 @@ // If settings.analysisFlag is null if (!settings.analysisFlag) settings.analysisFlag = { value_col: null, values: [] }; - + if (!settings.analysisFlag.value_col) settings.analysisFlag.value_col = null; + if (!(settings.analysisFlag.values instanceof Array)) { + settings.analysisFlag.values = + typeof settings.analysisFlag.values == 'string' + ? [settings.analysisFlag.values] + : []; + } //if it is null, set settings.baseline.value_col to settings.studyday_col. if (!settings.baseline) settings.baseline = { value_col: null, values: [] }; - if (settings.baseline.values.length == 0) settings.baseline.values = [0]; if (!settings.baseline.value_col) settings.baseline.value_col = settings.studyday_col; + if (!(settings.baseline.values instanceof Array)) { + settings.baseline.values = + typeof settings.baseline.values == 'string' ? [settings.baseline.values] : []; + } //parse x_ and y_options to array if needed - if (typeof settings.x_options == 'string') settings.x_options = [settings.x_options]; - if (typeof settings.y_options == 'string') settings.y_options = [settings.y_options]; + if (!(settings.x_options instanceof Array)) { + settings.x_options = typeof settings.x_options == 'string' ? [settings.x_options] : []; + } + + if (!(settings.x_options instanceof Array)) { + settings.y_options = typeof settings.y_options == 'string' ? [settings.y_options] : []; + } // track initial Cutpoint (lets us detect when cutpoint should change) settings.cuts.x = settings.x.column; From d0b6098403cf8a5eb61d3e6f31a36f428b1b2ae2 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Mon, 22 Apr 2019 15:54:49 -0400 Subject: [PATCH 87/98] handle vectors in trimData --- R/trimData.R | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/R/trimData.R b/R/trimData.R index b7e72c54..a3b99af3 100644 --- a/R/trimData.R +++ b/R/trimData.R @@ -28,23 +28,39 @@ trimData <- function(data, settings, chart="edish"){ # Add items in vectors to list individually dataVectorKeys <- allKeys %>% filter(.data$setting_type =="vector") %>% pull(.data$text_key) %>% textKeysToList() + + vectorVars <- list() + for(key in dataVectorKeys){ current<-getSettingValue(key, settings=settings) + if (length(current) > 0 ) { - for (i in 1:length(current)){ - newKey <- key - newKey[[1+length(newKey)]]<-i - sub <- current[[i]] - if(typeof(sub)=="list"){ - newKey[[1+length(newKey)]]<-"value_col" + # handle vectors separately + if (typeof(current) == "character") { + for (i in 1:length(current)){ + vectorVars <- append(vectorVars,current[i]) + } + } else { + for (i in 1:length(current)){ + newKey <- key + newKey[[1+length(newKey)]]<-i + sub <- current[[i]] + if(typeof(sub)=="list"){ + newKey[[1+length(newKey)]]<-"value_col" + } + dataKeys[[1+length(dataKeys)]]<-newKey } - dataKeys[[1+length(dataKeys)]]<-newKey } } } - settings_values <- map(dataKeys, function(x) {return(getSettingValue(x, settings))}) + + settings_values <- map(dataKeys, function(x) {return(getSettingValue(x, settings))}) + + #add variables from vectors to the list before intersect + settings_values <- c(settings_values, vectorVars) + common_cols <- intersect(col_names,settings_values) data_subset <- select(data, unlist(common_cols)) From 3a9ed5d0cf0701ea4cbe3dee91286537c0780508 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 10:28:09 -0400 Subject: [PATCH 88/98] fix getSettingValue instead --- R/getSettingValue.R | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/R/getSettingValue.R b/R/getSettingValue.R index 9794482c..6d213d5e 100644 --- a/R/getSettingValue.R +++ b/R/getSettingValue.R @@ -23,16 +23,21 @@ getSettingValue <- function(key,settings){ # Get the value for the first key firstKey <- key[[1]] value <- settings[[firstKey]] - - if(length(key)>1 ){ #If there are more keys and the value is a list, iterate if(typeof(value)=="list"){ value<-getSettingValue(key[2:length(key)],value) + #If position is provided and the value is a character vector + }else if(typeof(value) == "character"){ + value<-value[[key[[2]]]] }else{ #If there are more keys, but the value is not a list, return NULL value<-NULL } + } else if (length(value) >1) { + } + + return(value) } \ No newline at end of file From 3f46a62f5d57004cdde5359601f99665bd7ba337 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 10:29:50 -0400 Subject: [PATCH 89/98] and get rid of old fix --- R/getSettingValue.R | 5 ++--- R/trimData.R | 36 ++++++++++-------------------------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/R/getSettingValue.R b/R/getSettingValue.R index 6d213d5e..f13d3c68 100644 --- a/R/getSettingValue.R +++ b/R/getSettingValue.R @@ -23,6 +23,8 @@ getSettingValue <- function(key,settings){ # Get the value for the first key firstKey <- key[[1]] value <- settings[[firstKey]] + + if(length(key)>1 ){ #If there are more keys and the value is a list, iterate if(typeof(value)=="list"){ @@ -35,9 +37,6 @@ getSettingValue <- function(key,settings){ value<-NULL } } else if (length(value) >1) { - } - - return(value) } \ No newline at end of file diff --git a/R/trimData.R b/R/trimData.R index a3b99af3..70e05af6 100644 --- a/R/trimData.R +++ b/R/trimData.R @@ -19,48 +19,32 @@ trimData <- function(data, settings, chart="edish"){ - + ## Remove columns not in settings ## col_names <- colnames(data) - + allKeys <- getSettingsMetadata(charts=chart, filter_expr = .data$column_mapping, cols = c("text_key","setting_type")) dataKeys <- allKeys %>% filter(.data$setting_type !="vector") %>% pull(.data$text_key) %>% textKeysToList() - + # Add items in vectors to list individually dataVectorKeys <- allKeys %>% filter(.data$setting_type =="vector") %>% pull(.data$text_key) %>% textKeysToList() - - vectorVars <- list() - for(key in dataVectorKeys){ current<-getSettingValue(key, settings=settings) - if (length(current) > 0 ) { - # handle vectors separately - if (typeof(current) == "character") { - for (i in 1:length(current)){ - vectorVars <- append(vectorVars,current[i]) - } - } else { - for (i in 1:length(current)){ - newKey <- key - newKey[[1+length(newKey)]]<-i - sub <- current[[i]] - if(typeof(sub)=="list"){ - newKey[[1+length(newKey)]]<-"value_col" - } - dataKeys[[1+length(dataKeys)]]<-newKey + for (i in 1:length(current)){ + newKey <- key + newKey[[1+length(newKey)]]<-i + sub <- current[[i]] + if(typeof(sub)=="list"){ + newKey[[1+length(newKey)]]<-"value_col" } + dataKeys[[1+length(dataKeys)]]<-newKey } } } - - settings_values <- map(dataKeys, function(x) {return(getSettingValue(x, settings))}) - #add variables from vectors to the list before intersect - settings_values <- c(settings_values, vectorVars) - common_cols <- intersect(col_names,settings_values) data_subset <- select(data, unlist(common_cols)) From 3e5386ae0841d8503aa8d5aac5b501e03df1c8d2 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 11:01:23 -0400 Subject: [PATCH 90/98] remove stray else if --- R/getSettingValue.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/getSettingValue.R b/R/getSettingValue.R index f13d3c68..3f084fbb 100644 --- a/R/getSettingValue.R +++ b/R/getSettingValue.R @@ -36,7 +36,6 @@ getSettingValue <- function(key,settings){ #If there are more keys, but the value is not a list, return NULL value<-NULL } - } else if (length(value) >1) { } return(value) } \ No newline at end of file From d264bed167fd7dec986f44e7d109f9efe1745485 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 13:52:50 -0400 Subject: [PATCH 91/98] update eDish js --- inst/htmlwidgets/eDISH.yaml | 4 ++-- .../safetyedish.js | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) rename inst/htmlwidgets/lib/{safety-eDISH-0.16.5 => safety-eDISH-0.16.7}/safetyedish.js (99%) diff --git a/inst/htmlwidgets/eDISH.yaml b/inst/htmlwidgets/eDISH.yaml index 3faf2d99..48ec8a0e 100644 --- a/inst/htmlwidgets/eDISH.yaml +++ b/inst/htmlwidgets/eDISH.yaml @@ -9,6 +9,6 @@ dependencies: script: webcharts.js stylesheet: webcharts.css - name: safety-eDish - version: 0.16.5 - src: htmlwidgets/lib/safety-eDISH-0.16.5 + version: 0.16.7 + src: htmlwidgets/lib/safety-eDISH-0.16.7 script: safetyedish.js diff --git a/inst/htmlwidgets/lib/safety-eDISH-0.16.5/safetyedish.js b/inst/htmlwidgets/lib/safety-eDISH-0.16.7/safetyedish.js similarity index 99% rename from inst/htmlwidgets/lib/safety-eDISH-0.16.5/safetyedish.js rename to inst/htmlwidgets/lib/safety-eDISH-0.16.7/safetyedish.js index b70faecf..98f329ab 100644 --- a/inst/htmlwidgets/lib/safety-eDISH-0.16.5/safetyedish.js +++ b/inst/htmlwidgets/lib/safety-eDISH-0.16.7/safetyedish.js @@ -445,6 +445,11 @@ }); } + //parse details to array if needed + if (!(settings.details instanceof Array)) { + settings.details = typeof settings.details == 'string' ? [settings.details] : []; + } + //If [settings.details] is not specified: if (!settings.details) settings.details = defaultDetails; else { @@ -493,7 +498,7 @@ settings.x_options = typeof settings.x_options == 'string' ? [settings.x_options] : []; } - if (!(settings.x_options instanceof Array)) { + if (!(settings.y_options instanceof Array)) { settings.y_options = typeof settings.y_options == 'string' ? [settings.y_options] : []; } From 3fe82223ec8229f8cdaf6b614d195e37147d6d75 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 14:01:49 -0400 Subject: [PATCH 92/98] update webcharts js --- inst/htmlwidgets/eDISH.yaml | 4 +- .../webcharts.css | 0 .../webcharts.js | 69 +++++++++++++------ inst/htmlwidgets/safetyHistogram.yaml | 4 +- 4 files changed, 53 insertions(+), 24 deletions(-) rename inst/htmlwidgets/lib/{webcharts-1.11.3 => webcharts-1.11.5}/webcharts.css (100%) rename inst/htmlwidgets/lib/{webcharts-1.11.3 => webcharts-1.11.5}/webcharts.js (98%) diff --git a/inst/htmlwidgets/eDISH.yaml b/inst/htmlwidgets/eDISH.yaml index 48ec8a0e..443bcbff 100644 --- a/inst/htmlwidgets/eDISH.yaml +++ b/inst/htmlwidgets/eDISH.yaml @@ -4,8 +4,8 @@ dependencies: src: htmlwidgets/lib/d3-3.5.17 script: d3.v3.min.js - name: webcharts - version: 1.11.3 - src: htmlwidgets/lib/webcharts-1.11.3 + version: 1.11.5 + src: htmlwidgets/lib/webcharts-1.11.5 script: webcharts.js stylesheet: webcharts.css - name: safety-eDish diff --git a/inst/htmlwidgets/lib/webcharts-1.11.3/webcharts.css b/inst/htmlwidgets/lib/webcharts-1.11.5/webcharts.css similarity index 100% rename from inst/htmlwidgets/lib/webcharts-1.11.3/webcharts.css rename to inst/htmlwidgets/lib/webcharts-1.11.5/webcharts.css diff --git a/inst/htmlwidgets/lib/webcharts-1.11.3/webcharts.js b/inst/htmlwidgets/lib/webcharts-1.11.5/webcharts.js similarity index 98% rename from inst/htmlwidgets/lib/webcharts-1.11.3/webcharts.js rename to inst/htmlwidgets/lib/webcharts-1.11.5/webcharts.js index e6fe9061..24ad10ef 100644 --- a/inst/htmlwidgets/lib/webcharts-1.11.3/webcharts.js +++ b/inst/htmlwidgets/lib/webcharts-1.11.5/webcharts.js @@ -6,7 +6,7 @@ : (global.webCharts = factory(global.d3)); })(typeof self !== 'undefined' ? self : this, function(d3) { 'use strict'; - var version = '1.11.3'; + var version = '1.11.5'; function init(data) { var _this = this; @@ -518,7 +518,7 @@ if (this.filters.length) { this.filters.forEach(function(filter) { _this.filtered_data = _this.filtered_data.filter(function(d) { - return filter.val === 'All' + return filter.all === true && filter.index === 0 ? d : filter.val instanceof Array ? filter.val.indexOf(d[filter.col]) > -1 @@ -1092,7 +1092,7 @@ if (this.filters.length) { this.filters.forEach(function(e) { filtered = filtered.filter(function(d) { - return e.val === 'All' + return e.all === true && e.index === 0 ? d : e.val instanceof Array ? e.val.indexOf(d[e.col]) > -1 @@ -1831,6 +1831,7 @@ function drawBars(marks) { var _this = this; + var chart = this; var rawData = this.raw_data; var config = this.config; @@ -1896,7 +1897,7 @@ .attr('class', function(d) { return 'wc-data-mark bar ' + d.key; }) - .style('clip-path', 'url(#' + this.id + ')') + .style('clip-path', 'url(#' + chart.id + ')') .attr('y', this.y(0)) .attr('height', 0) .append('title'); @@ -2029,7 +2030,7 @@ .attr('class', function(d) { return 'wc-data-mark bar ' + d.key; }) - .style('clip-path', 'url(#' + this.id + ')') + .style('clip-path', 'url(#' + chart.id + ')') .attr('x', this.x(0)) .attr('width', 0) .append('title'); @@ -2157,7 +2158,7 @@ .attr('class', function(d) { return 'wc-data-mark bar ' + d.key; }) - .style('clip-path', 'url(#' + this.id + ')') + .style('clip-path', 'url(#' + chart.id + ')') .attr('y', this.y(0)) .attr('height', 0) .append('title'); @@ -2267,7 +2268,7 @@ .attr('class', function(d) { return 'wc-data-mark bar ' + d.key; }) - .style('clip-path', 'url(#' + this.id + ')') + .style('clip-path', 'url(#' + chart.id + ')') .attr('x', this.x(0)) .attr('width', 0) .append('title'); @@ -2362,6 +2363,7 @@ function drawLines(marks) { var _this = this; + var chart = this; var config = this.config; var line = d3.svg .line() @@ -2409,6 +2411,7 @@ var linePaths = line_grps .select('path') .attr('class', 'wc-data-mark') + .style('clip-path', 'url(#' + chart.id + ')') .datum(function(d) { return d.values; }) @@ -2458,6 +2461,7 @@ function drawPoints(marks) { var _this = this; + var chart = this; var config = this.config; var point_supergroups = this.svg.selectAll('.point-supergroup').data(marks, function(d, i) { @@ -2496,6 +2500,7 @@ //static attributes points .select('circle') + .style('clip-path', 'url(#' + chart.id + ')') .attr( 'fill-opacity', config.fill_opacity || config.fill_opacity === 0 ? config.fill_opacity : 0.6 @@ -2568,6 +2573,7 @@ function drawText(marks) { var _this = this; + var chart = this; var config = this.config; var textSupergroups = this.svg.selectAll('.text-supergroup').data(marks, function(d, i) { @@ -2610,7 +2616,7 @@ texts.each(attachMarks); // parse text like tooltips - texts.select('text').text(function(d) { + texts.select('text').style('clip-path', 'url(#' + chart.id + ')').text(function(d) { var tt = d.mark.text || ''; var xformat = config.x.summary === 'percent' ? d3.format('0%') @@ -3061,13 +3067,16 @@ } function makeSubsetterControl(control, control_wrap) { - var targets = this.targets; + var targets = this.targets; // associated charts and tables. + + //dropdown selection var changer = control_wrap .append('select') - .attr('class', 'changer') + .classed('changer', true) .attr('multiple', control.multiple ? true : null) .datum(control); + //dropdown option data var option_data = control.values ? control.values : d3 @@ -3080,17 +3089,24 @@ return f; }) ) - .values(); - option_data.sort(naturalSorter); + .values() + .sort(naturalSorter); // only sort when values are derived + //initial dropdown option control.start = control.start ? control.start : control.loose ? option_data[0] : null; + //conditionally add All option if (!control.multiple && !control.start) { option_data.unshift('All'); + control.all = true; + } else { + control.all = false; } + //what does loose mean? control.loose = !control.loose && control.start ? true : control.loose; + //dropdown options selection var options = changer .selectAll('option') .data(option_data) @@ -3103,6 +3119,7 @@ return d === control.start; }); + //define filter object for each associated target targets.forEach(function(e) { var match = e.filters .slice() @@ -3113,16 +3130,20 @@ if (match > -1) { e.filters[match] = { col: control.value_col, - val: control.start ? control.start : 'All', + val: control.start ? control.start : !control.multiple ? 'All' : option_data, + index: 0, choices: option_data, - loose: control.loose + loose: control.loose, + all: control.all }; } else { e.filters.push({ col: control.value_col, - val: control.start ? control.start : 'All', + val: control.start ? control.start : !control.multiple ? 'All' : option_data, + index: 0, choices: option_data, - loose: control.loose + loose: control.loose, + all: control.all }); } }); @@ -3139,6 +3160,7 @@ } } + //add event listener to control changer.on('change', function(d) { if (control.multiple) { var values = options @@ -3152,8 +3174,10 @@ var new_filter = { col: control.value_col, val: values, + index: null, // could specify an array of indices but seems like a waste of resources give it doesn't inform anything without an overall 'All' choices: option_data, - loose: control.loose + loose: control.loose, + all: control.all }; targets.forEach(function(e) { setSubsetter(e, new_filter); @@ -3165,11 +3189,14 @@ }); } else { var value = d3.select(this).select('option:checked').property('text'); + var index = d3.select(this).select('option:checked').property('index'); var _new_filter = { col: control.value_col, val: value, + index: index, choices: option_data, - loose: control.loose + loose: control.loose, + all: control.all }; targets.forEach(function(e) { setSubsetter(e, _new_filter); @@ -3273,7 +3300,8 @@ this.filters && this.filters.some(function(filter) { return ( - (typeof filter.val === 'string' && filter.val !== 'All') || + (typeof filter.val === 'string' && + !(filter.all === true && filter.index === 0)) || (Array.isArray(filter.val) && filter.val.length < filter.choices.length) ); }) @@ -3282,7 +3310,8 @@ this.filters .filter(function(filter) { return ( - (typeof filter.val === 'string' && filter.val !== 'All') || + (typeof filter.val === 'string' && + !(filter.all === true && filter.index === 0)) || (Array.isArray(filter.val) && filter.val.length < filter.choices.length) ); }) diff --git a/inst/htmlwidgets/safetyHistogram.yaml b/inst/htmlwidgets/safetyHistogram.yaml index 4277978b..1982a9ac 100644 --- a/inst/htmlwidgets/safetyHistogram.yaml +++ b/inst/htmlwidgets/safetyHistogram.yaml @@ -4,8 +4,8 @@ dependencies: src: htmlwidgets/lib/d3-3.5.17 script: d3.v3.min.js - name: webcharts - version: 1.11.3 - src: htmlwidgets/lib/webcharts-1.11.3 + version: 1.11.5 + src: htmlwidgets/lib/webcharts-1.11.5 script: webcharts.js stylesheet: webcharts.css - name: safety-histogram From 92757737ac6eb64eb0ad36452d9cb873aebb19b9 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Tue, 23 Apr 2019 11:25:32 -0700 Subject: [PATCH 93/98] update getSettingValue and add tests --- R/getSettingValue.R | 6 +++--- tests/testthat/test_getSettingValue.R | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/R/getSettingValue.R b/R/getSettingValue.R index 3f084fbb..fac49b1a 100644 --- a/R/getSettingValue.R +++ b/R/getSettingValue.R @@ -29,9 +29,9 @@ getSettingValue <- function(key,settings){ #If there are more keys and the value is a list, iterate if(typeof(value)=="list"){ value<-getSettingValue(key[2:length(key)],value) - #If position is provided and the value is a character vector - }else if(typeof(value) == "character"){ - value<-value[[key[[2]]]] + #If position is provided and the value is a vector + }else if(typeof(key[[2]])=="double" & length(value)>=key[[2]] & length(key)==2){ + value<-value[[key[[2]]]] }else{ #If there are more keys, but the value is not a list, return NULL value<-NULL diff --git a/tests/testthat/test_getSettingValue.R b/tests/testthat/test_getSettingValue.R index 59f5ac00..cf577d38 100644 --- a/tests/testthat/test_getSettingValue.R +++ b/tests/testthat/test_getSettingValue.R @@ -17,6 +17,23 @@ test_that("different data types for `key` parameter work as expected",{ expect_equal(getSettingValue(key=list("measure_values",1),settings=testSettings),"Aminotransferase, alanine (ALT)") }) +test_that("can get a specific item out of a vector if desired",{ + filter_vector = list(filters=c("SEX","AGE","RACE")) + filter_list = list(filters=list("SEX","AGE","RACE")) + expect_equal(getSettingValue(key=list("filters"),settings=filter_vector),c("SEX","AGE","RACE")) + expect_equal(getSettingValue(key=list("filters",2),settings=filter_vector),"AGE") + expect_null(getSettingValue(key=list("filters",2,"test"),settings=filter_vector)) + expect_null(getSettingValue(key=list("filters",4),settings=filter_vector)) + expect_null(getSettingValue(key=list("filters","4"),settings=filter_vector)) + + + + expect_equal(getSettingValue(key=list("filters"),settings=filter_list),list("SEX","AGE","RACE")) + expect_equal(getSettingValue(key=list("filters",2),settings=filter_list),"AGE") + + +}) + test_that("returns null if the setting isn't found",{ expect_null(getSettingValue(key="testKeyandmore",settings=list(testKey="ABC"))) expect_null(getSettingValue(key=c("a","b","c"),settings=list(testKey="ABC"))) From 7c1b0e4c3048697c1c0cc7b723abb8d0803225b6 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 14:44:47 -0400 Subject: [PATCH 94/98] fix array getSettingValue bug --- R/getSettingValue.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/getSettingValue.R b/R/getSettingValue.R index fac49b1a..94681089 100644 --- a/R/getSettingValue.R +++ b/R/getSettingValue.R @@ -30,7 +30,7 @@ getSettingValue <- function(key,settings){ if(typeof(value)=="list"){ value<-getSettingValue(key[2:length(key)],value) #If position is provided and the value is a vector - }else if(typeof(key[[2]])=="double" & length(value)>=key[[2]] & length(key)==2){ + }else if(typeof(key[[2]])=="integer" & length(value)>=key[[2]] & length(key)==2){ value<-value[[key[[2]]]] }else{ #If there are more keys, but the value is not a list, return NULL From 29b83a72a5dc75cfd1e215948e63d6cfdd5553f0 Mon Sep 17 00:00:00 2001 From: Preston Burns Date: Tue, 23 Apr 2019 15:04:09 -0400 Subject: [PATCH 95/98] change getSettingValue if to is.numeric --- R/getSettingValue.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/getSettingValue.R b/R/getSettingValue.R index 94681089..dc8a7078 100644 --- a/R/getSettingValue.R +++ b/R/getSettingValue.R @@ -30,7 +30,7 @@ getSettingValue <- function(key,settings){ if(typeof(value)=="list"){ value<-getSettingValue(key[2:length(key)],value) #If position is provided and the value is a vector - }else if(typeof(key[[2]])=="integer" & length(value)>=key[[2]] & length(key)==2){ + }else if(is.numeric(key[[2]]) & length(value)>=key[[2]] & length(key)==2){ value<-value[[key[[2]]]] }else{ #If there are more keys, but the value is not a list, return NULL From 34e52a1928d92c739885932e51555d02a3f73900 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Tue, 23 Apr 2019 16:24:38 -0400 Subject: [PATCH 96/98] remove export button if no edish selected --- .../modules/renderChart/render_edish_chart.R | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/inst/eDISH_app/modules/renderChart/render_edish_chart.R b/inst/eDISH_app/modules/renderChart/render_edish_chart.R index 8c5aa75c..4e9a1ff4 100644 --- a/inst/eDISH_app/modules/renderChart/render_edish_chart.R +++ b/inst/eDISH_app/modules/renderChart/render_edish_chart.R @@ -32,8 +32,15 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ # insert export chart button if settings pass validation # remove button if validation fails observeEvent(valid(), { + if (is.null(valid())){ + valid <- FALSE + } else { + valid <- valid() + } + removeUI(selector = paste0("#", ns("download"))) - if (valid()==TRUE){ + + if (valid==TRUE){ insertUI ( selector = "div.container-fluid", where = "beforeEnd", @@ -47,7 +54,7 @@ render_edish_chart <- function(input, output, session, data, settings, valid){ else { removeUI(selector = paste0("#", ns("download"))) } - }) + }, ignoreNULL = FALSE) # Set up report generation on download button click From a11aed3d80320dfdf76364b9708812794e91c136 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 24 Apr 2019 09:25:39 -0700 Subject: [PATCH 97/98] show marks in white when chart is selected --- inst/eDISH_app/www/index.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/inst/eDISH_app/www/index.css b/inst/eDISH_app/www/index.css index 1153f43c..09a2280e 100644 --- a/inst/eDISH_app/www/index.css +++ b/inst/eDISH_app/www/index.css @@ -46,6 +46,8 @@ padding-left:0.3em; } + + #nav_id li.dropdown ul.dropdown-menu li a.invalid:after{ font-family: "Font Awesome 5 Free"; font-weight: 900; @@ -54,6 +56,10 @@ padding-left:0.3em; } +/* chart validation formatting */ +#nav_id li.dropdown ul.dropdown-menu li.active a:after{ + color: white; +} /* Setting Validation Coloring */ From a630d414faea1969fafb90a287fbebd916d47fd5 Mon Sep 17 00:00:00 2001 From: bzkrouse Date: Wed, 24 Apr 2019 12:35:51 -0400 Subject: [PATCH 98/98] force chart input to update upon App startup --- inst/eDISH_app/modules/renderSettings/renderSettings.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/eDISH_app/modules/renderSettings/renderSettings.R b/inst/eDISH_app/modules/renderSettings/renderSettings.R index 9ff7693a..066e2520 100644 --- a/inst/eDISH_app/modules/renderSettings/renderSettings.R +++ b/inst/eDISH_app/modules/renderSettings/renderSettings.R @@ -154,6 +154,8 @@ renderSettings <- function(input, output, session, data, settings, status){ }, ignoreNULL=FALSE) ## input$charts = NULL if none are selected + # ensure outputs update upon app startup + outputOptions(output, "charts_wrap_ui", suspendWhenHidden = FALSE) outputOptions(output, "data_mapping_ui", suspendWhenHidden = FALSE) outputOptions(output, "measure_settings_ui", suspendWhenHidden = FALSE) outputOptions(output, "appearance_settings_ui", suspendWhenHidden = FALSE)