From c4081942ba53ee81d4e13dfa47b0c3d3cbc710c2 Mon Sep 17 00:00:00 2001
From: jwildfire
Date: Mon, 19 Apr 2021 10:57:42 -0400
Subject: [PATCH 1/5] clean up inst. fix #506
---
R/app_ui.R | 2 +-
inst/config/mappings/aes.yaml | 1 -
inst/examples/custom_charts/custom_ggplot.R | 139 -
inst/examples/custom_charts/custom_module.R | 14 -
inst/examples/custom_charts/custom_plotly.R | 140 -
inst/safetyGraphics_app/global.R | 118 -
.../modules/config/config.R | 73 -
.../modules/config/configUI.R | 26 -
.../modules/dataUpload/dataUpload.R | 207 -
.../modules/dataUpload/dataUploadUI.R | 44 -
.../modules/renderChart/renderChart.R | 79 -
.../modules/renderChart/renderChartUI.R | 17 -
.../modules/renderReports/renderReports.R | 100 -
.../modules/renderReports/renderReportsUI.R | 28 -
.../renderReports/safetyGraphicsReport.Rmd | 118 -
.../modules/renderSettings/renderSettings.R | 354 -
.../modules/renderSettings/renderSettingsUI.R | 11 -
.../renderSettings/util/createControl.R | 123 -
.../renderSettings/util/createSettingLabel.R | 13 -
.../util/createSettingsSection.R | 24 -
.../renderSettings/util/createSettingsUI.R | 23 -
.../renderSettings/util/updateSettingStatus.R | 29 -
inst/safetyGraphics_app/server.R | 171 -
.../tests/defaultPath-expected/001.json | 22 -
.../tests/defaultPath-expected/001.png | Bin 80017 -> 0 bytes
.../tests/defaultPath-expected/002.json | 387 -
.../tests/defaultPath-expected/002.png | Bin 104261 -> 0 bytes
inst/safetyGraphics_app/tests/defaultPath.R | 36 -
.../tests/fullSDTM-expected/001.json | 137 -
.../tests/fullSDTM-expected/002.json | 22 -
.../tests/fullSDTM-expected/002.png | Bin 90740 -> 0 bytes
.../tests/fullSDTM-expected/003.json | 387 -
.../tests/fullSDTM-expected/003.png | Bin 104132 -> 0 bytes
inst/safetyGraphics_app/tests/fullSDTM.R | 52 -
inst/safetyGraphics_app/tests/fullSDTM.csv | 24670 ----------------
.../tests/noStandard-expected/001.json | 136 -
.../tests/noStandard-expected/002.json | 22 -
.../tests/noStandard-expected/002.png | Bin 8050 -> 0 bytes
.../tests/noStandard-expected/003.json | 387 -
.../tests/noStandard-expected/003.png | Bin 100379 -> 0 bytes
inst/safetyGraphics_app/tests/noStandard.R | 52 -
inst/safetyGraphics_app/tests/noStandard.csv | 24670 ----------------
.../tests/partialSDTM-expected/001.json | 137 -
.../tests/partialSDTM-expected/002.json | 22 -
.../tests/partialSDTM-expected/002.png | Bin 8050 -> 0 bytes
.../tests/partialSDTM-expected/003.json | 387 -
.../tests/partialSDTM-expected/003.png | Bin 102854 -> 0 bytes
.../tests/partialSDTM-expected/004.json | 22 -
.../tests/partialSDTM-expected/004.png | Bin 90740 -> 0 bytes
.../tests/partialSDTM-expected/005.json | 387 -
.../tests/partialSDTM-expected/005.png | Bin 103643 -> 0 bytes
inst/safetyGraphics_app/tests/partialSDTM.R | 88 -
inst/safetyGraphics_app/tests/partialSDTM.csv | 24670 ----------------
inst/safetyGraphics_app/ui.R | 58 -
inst/{safetyGraphics_app => }/www/index.css | 0
.../www/index_old.css | 0
56 files changed, 1 insertion(+), 78604 deletions(-)
delete mode 100644 inst/config/mappings/aes.yaml
delete mode 100644 inst/examples/custom_charts/custom_ggplot.R
delete mode 100644 inst/examples/custom_charts/custom_module.R
delete mode 100644 inst/examples/custom_charts/custom_plotly.R
delete mode 100644 inst/safetyGraphics_app/global.R
delete mode 100644 inst/safetyGraphics_app/modules/config/config.R
delete mode 100644 inst/safetyGraphics_app/modules/config/configUI.R
delete mode 100644 inst/safetyGraphics_app/modules/dataUpload/dataUpload.R
delete mode 100644 inst/safetyGraphics_app/modules/dataUpload/dataUploadUI.R
delete mode 100644 inst/safetyGraphics_app/modules/renderChart/renderChart.R
delete mode 100644 inst/safetyGraphics_app/modules/renderChart/renderChartUI.R
delete mode 100644 inst/safetyGraphics_app/modules/renderReports/renderReports.R
delete mode 100644 inst/safetyGraphics_app/modules/renderReports/renderReportsUI.R
delete mode 100644 inst/safetyGraphics_app/modules/renderReports/safetyGraphicsReport.Rmd
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/renderSettings.R
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/renderSettingsUI.R
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/util/createControl.R
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/util/createSettingLabel.R
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/util/createSettingsSection.R
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/util/createSettingsUI.R
delete mode 100644 inst/safetyGraphics_app/modules/renderSettings/util/updateSettingStatus.R
delete mode 100644 inst/safetyGraphics_app/server.R
delete mode 100644 inst/safetyGraphics_app/tests/defaultPath-expected/001.json
delete mode 100644 inst/safetyGraphics_app/tests/defaultPath-expected/001.png
delete mode 100644 inst/safetyGraphics_app/tests/defaultPath-expected/002.json
delete mode 100644 inst/safetyGraphics_app/tests/defaultPath-expected/002.png
delete mode 100644 inst/safetyGraphics_app/tests/defaultPath.R
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM-expected/001.json
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM-expected/002.json
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM-expected/002.png
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM-expected/003.json
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM-expected/003.png
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM.R
delete mode 100644 inst/safetyGraphics_app/tests/fullSDTM.csv
delete mode 100644 inst/safetyGraphics_app/tests/noStandard-expected/001.json
delete mode 100644 inst/safetyGraphics_app/tests/noStandard-expected/002.json
delete mode 100644 inst/safetyGraphics_app/tests/noStandard-expected/002.png
delete mode 100644 inst/safetyGraphics_app/tests/noStandard-expected/003.json
delete mode 100644 inst/safetyGraphics_app/tests/noStandard-expected/003.png
delete mode 100644 inst/safetyGraphics_app/tests/noStandard.R
delete mode 100644 inst/safetyGraphics_app/tests/noStandard.csv
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/001.json
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/002.json
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/002.png
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/003.json
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/003.png
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/004.json
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/004.png
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/005.json
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM-expected/005.png
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM.R
delete mode 100644 inst/safetyGraphics_app/tests/partialSDTM.csv
delete mode 100644 inst/safetyGraphics_app/ui.R
rename inst/{safetyGraphics_app => }/www/index.css (100%)
rename inst/{safetyGraphics_app => }/www/index_old.css (100%)
diff --git a/R/app_ui.R b/R/app_ui.R
index 62e32f1f..aec3a13e 100644
--- a/R/app_ui.R
+++ b/R/app_ui.R
@@ -14,7 +14,7 @@ app_ui <- function(meta, domainData, mapping, standards){
app_css <- NULL
for(lib in .libPaths()){
if(is.null(app_css)){
- css_path <- paste(lib,'safetyGraphics','safetyGraphics_app', 'www','index.css', sep="/")
+ css_path <- paste(lib,'safetyGraphics', 'www','index.css', sep="/")
if(file.exists(css_path)) app_css <- HTML(readLines(css_path))
}
}
diff --git a/inst/config/mappings/aes.yaml b/inst/config/mappings/aes.yaml
deleted file mode 100644
index 9a5ac690..00000000
--- a/inst/config/mappings/aes.yaml
+++ /dev/null
@@ -1 +0,0 @@
-- text_key: id_col\n col_key: id_col\n field_key: .na.character\n type: column\n label: ID column\n description: Unique subject identifier variable name.\n multiple: no\n standard_adam: USUBJID\n standard_sdtm: USUBJID\n- text_key: seq_col\n col_key: seq_col\n field_key: .na.character\n type: column\n label: Sequence column\n description: Event sequence number variable name\n multiple: no\n standard_adam: ASEQ\n standard_sdtm: AESEQ\n- text_key: stdy_col\n col_key: stdy_col\n field_key: .na.character\n type: column\n label: AE Start day column\n description: Event start day variable name\n multiple: no\n standard_adam: ASTDY\n standard_sdtm: AESTDY\n- text_key: endy_col\n col_key: endy_col\n field_key: .na.character\n type: column\n label: AE End day column\n description: Event end day variable name\n multiple: no\n standard_adam: AENDY\n standard_sdtm: AEENDY\n- text_key: term_col\n col_key: term_col\n field_key: .na.character\n type: column\n label: Preferred Term Column\n description: verbatim adverse event text variable name\n multiple: no\n standard_adam: AETERM\n standard_sdtm: AETERM\n- text_key: bodsys_col\n col_key: bodsys_col\n field_key: .na.character\n type: column\n label: AE Body System\n description: AE Body System\n multiple: no\n standard_adam: AEBODSYS\n standard_sdtm: AEBODSYS\n- text_key: detail_col\n col_key: detail_col\n field_key: .na.character\n type: column\n label: Details\n description: Details\n multiple: yes\n standard_adam: .na.character\n standard_sdtm: .na.character\n- text_key: filter_cols\n col_key: filter_cols\n field_key: .na.character\n type: column\n label: Filters\n description: Filters\n multiple: yes\n standard_adam: .na.character\n standard_sdtm: .na.character\n
\ No newline at end of file
diff --git a/inst/examples/custom_charts/custom_ggplot.R b/inst/examples/custom_charts/custom_ggplot.R
deleted file mode 100644
index 08c3f3db..00000000
--- a/inst/examples/custom_charts/custom_ggplot.R
+++ /dev/null
@@ -1,139 +0,0 @@
-library(ggplot2)
-library(dplyr)
-library(tidyr)
-
-
-custom_ggplot <- function(data, settings){
-
- id_col <- settings[["id_col"]]
- value_col <- settings[["value_col"]]
- measure_col <- settings[["measure_col"]]
- measure_alt <- settings[["measure_values"]][["ALT"]]
- measure_tb <- settings[["measure_values"]][["TB"]]
- group <- settings[["group_cols"]][1]
- normal_col_high <- settings[["normal_col_high"]]
- studyday_col <- settings[["studyday_col"]]
-
- if (!is.null(group)){
- d <- data %>%
- select(id_col = id_col,
- measure_col = measure_col,
- value_col = value_col,
- normal_col_high = normal_col_high,
- studyday_col = studyday_col,
- group = group) %>%
- filter(measure_col %in% c(measure_alt, measure_tb)) %>%
- group_by(id_col, measure_col) %>%
- filter(value_col==max(value_col)) %>%
- mutate(peak_val_uln = value_col/normal_col_high) %>%
- unique %>%
- slice(1) %>%
- group_by(id_col) %>%
- unique %>%
- mutate(days_between = abs(studyday_col[1]-studyday_col[2])) %>%
- select(id_col,group, measure_col, peak_val_uln, days_between) %>%
- unique %>%
- spread(measure_col, peak_val_uln) %>%
- setNames(., c("id","group","days_between", "alt","tb")) %>%
- na.omit %>%
- ungroup %>%
- mutate(hys_law = 100*sum(alt>3 & tb>2)/n(),
- hyper = 100*sum(alt <3 & tb >2)/n(),
- temple = 100*sum(alt >3 & tb <2)/n(),
- normal = 100*sum(alt <3 & tb <2)/n()
- ) %>%
- mutate_at(vars(hys_law:normal), ~ paste0(format(round(.,1), nsmall=1), "%"))
-
- plottext <- data.frame(
- xpos = c(Inf,-Inf,Inf,-Inf),
- ypos = c(Inf, Inf,-Inf,-Inf),
- hjust = c(1.1,-0.1,1.1,-0.1) ,
- vjust = c(1.3,1.3,-0.3,-0.3),
- text1 = c("Possible Hy's Law Range",
- "Hyperbilirubinemia",
- "Temple's Corollary",
- "Normal Range"),
- text2 = as.character(unique(select(d, hys_law:normal))))
-
- p <- ggplot(data=d, aes(alt, tb)) +
- geom_point(size = 2, alpha=0.7,aes(color=group, shape=factor(days_between<=30))) +
- scale_shape_manual(values = c(1, 19), guide=FALSE) +
- theme_bw() +
- geom_hline(yintercept = 2, lty=2, color="darkgray")+
- geom_vline(xintercept = 3, lty=2, color="darkgray") +
- theme(legend.position = "top",
- legend.title=element_blank()) +
- scale_x_continuous(name = paste(measure_alt, "[xULN]"),
- breaks = seq(0, round(max(d$alt)+0.5,1), 0.5),
- labels = function(x) sprintf("%.1f", x))+
- scale_y_continuous(name = paste(measure_tb, "[xULN]"),
- breaks = seq(0, round(max(d$tb),1), 1),
- labels = function(x) sprintf("%.1f", x)) +
- geom_text(data=plottext,
- aes(x=xpos,y=ypos,
- hjust=hjust,vjust=vjust,
- label=paste(text1, text2)),
- color = 'black', size=3)
- } else {
- d <- data %>%
- select(id_col = id_col,
- measure_col = measure_col,
- value_col = value_col,
- normal_col_high = normal_col_high,
- studyday_col = studyday_col) %>%
- filter(measure_col %in% c(measure_alt, measure_tb)) %>%
- group_by(id_col, measure_col) %>%
- filter(value_col==max(value_col)) %>%
- mutate(peak_val_uln = value_col/normal_col_high) %>%
- unique %>%
- slice(1) %>%
- group_by(id_col) %>%
- unique %>%
- mutate(days_between = abs(studyday_col[1]-studyday_col[2])) %>%
- select(id_col,measure_col, peak_val_uln, days_between) %>%
- unique %>%
- spread(measure_col, peak_val_uln) %>%
- setNames(., c("id","days_between", "alt","tb")) %>%
- na.omit %>%
- ungroup %>%
- mutate(hys_law = 100*sum(alt>3 & tb>2)/n(),
- hyper = 100*sum(alt <3 & tb >2)/n(),
- temple = 100*sum(alt >3 & tb <2)/n(),
- normal = 100*sum(alt <3 & tb <2)/n()
- ) %>%
- mutate_at(vars(hys_law:normal), ~ paste0(format(round(.,1), nsmall=1), "%"))
-
- plottext <- data.frame(
- xpos = c(Inf,-Inf,Inf,-Inf),
- ypos = c(Inf, Inf,-Inf,-Inf),
- hjust = c(1.1,-0.1,1.1,-0.1) ,
- vjust = c(1.3,1.3,-0.3,-0.3),
- text1 = c("Possible Hy's Law Range",
- "Hyperbilirubinemia",
- "Temple's Corollary",
- "Normal Range"),
- text2 = as.character(unique(select(d, hys_law:normal))))
-
- p <- ggplot(data=d, aes(alt, tb)) +
- geom_point(size = 2, alpha=0.7,aes(shape=factor(days_between<=30))) +
- scale_shape_manual(values = c(1, 19), guide=FALSE) +
- theme_bw() +
- geom_hline(yintercept = 2, lty=2, color="darkgray")+
- geom_vline(xintercept = 3, lty=2, color="darkgray") +
- theme(legend.position = "top",
- legend.title=element_blank()) +
- scale_x_continuous(name = paste(measure_alt, "[xULN]"),
- breaks = seq(0, round(max(d$alt)+0.5,1), 0.5),
- labels = function(x) sprintf("%.1f", x))+
- scale_y_continuous(name = paste(measure_tb, "[xULN]"),
- breaks = seq(0, round(max(d$tb),1), 1),
- labels = function(x) sprintf("%.1f", x)) +
- geom_text(data=plottext,
- aes(x=xpos,y=ypos,
- hjust=hjust,vjust=vjust,
- label=paste(text1, text2)),
- color = 'black', size=3)
- }
- return(p)
-}
-
diff --git a/inst/examples/custom_charts/custom_module.R b/inst/examples/custom_charts/custom_module.R
deleted file mode 100644
index d449c825..00000000
--- a/inst/examples/custom_charts/custom_module.R
+++ /dev/null
@@ -1,14 +0,0 @@
-
-custom_module_UI <- function(id) {
- ns <- NS(id)
- tagList(
- ## UI code here
- )
-}
-
-custom_module <- function(input, output, session, data, settings) {
-
- ## server code here
-
-}
-
\ No newline at end of file
diff --git a/inst/examples/custom_charts/custom_plotly.R b/inst/examples/custom_charts/custom_plotly.R
deleted file mode 100644
index 8bbcbfc5..00000000
--- a/inst/examples/custom_charts/custom_plotly.R
+++ /dev/null
@@ -1,140 +0,0 @@
-library(ggplot2)
-library(plotly)
-library(dplyr)
-library(tidyr)
-
-
-custom_plotly <- function(data, settings){
-
- id_col <- settings[["id_col"]]
- value_col <- settings[["value_col"]]
- measure_col <- settings[["measure_col"]]
- measure_alt <- settings[["measure_values"]][["ALT"]]
- measure_tb <- settings[["measure_values"]][["TB"]]
- group <- settings[["group_cols"]][1]
- normal_col_high <- settings[["normal_col_high"]]
- studyday_col <- settings[["studyday_col"]]
-
- if (!is.null(group)){
- d <- data %>%
- select(id_col = id_col,
- measure_col = measure_col,
- value_col = value_col,
- normal_col_high = normal_col_high,
- studyday_col = studyday_col,
- group = group) %>%
- filter(measure_col %in% c(measure_alt, measure_tb)) %>%
- group_by(id_col, measure_col) %>%
- filter(value_col==max(value_col)) %>%
- mutate(peak_val_uln = value_col/normal_col_high) %>%
- unique %>%
- slice(1) %>%
- group_by(id_col) %>%
- unique %>%
- mutate(days_between = abs(studyday_col[1]-studyday_col[2])) %>%
- select(id_col,group, measure_col, peak_val_uln, days_between) %>%
- unique %>%
- spread(measure_col, peak_val_uln) %>%
- setNames(., c("id","group","days_between", "alt","tb")) %>%
- na.omit %>%
- ungroup %>%
- mutate(hys_law = 100*sum(alt>3 & tb>2)/n(),
- hyper = 100*sum(alt <3 & tb >2)/n(),
- temple = 100*sum(alt >3 & tb <2)/n(),
- normal = 100*sum(alt <3 & tb <2)/n()
- ) %>%
- mutate_at(vars(hys_law:normal), ~ paste0(format(round(.,1), nsmall=1), "%"))
-
- plottext <- data.frame(
- xpos = c(Inf,-Inf,Inf,-Inf),
- ypos = c(Inf, Inf,-Inf,-Inf),
- hjust = c(1.1,-0.1,1.1,-0.1) ,
- vjust = c(1.3,1.3,-0.3,-0.3),
- text1 = c("Possible Hy's Law Range",
- "Hyperbilirubinemia",
- "Temple's Corollary",
- "Normal Range"),
- text2 = as.character(unique(select(d, hys_law:normal))))
-
- p <- ggplot(data=d, aes(alt, tb)) +
- geom_point(size = 2, alpha=0.7,aes(color=group, shape=factor(days_between<=30))) +
- scale_shape_manual(values = c(1, 19), guide=FALSE) +
- theme_bw() +
- geom_hline(yintercept = 2, lty=2, color="darkgray")+
- geom_vline(xintercept = 3, lty=2, color="darkgray") +
- theme(legend.position = "top",
- legend.title=element_blank()) +
- scale_x_continuous(name = paste(measure_alt, "[xULN]"),
- breaks = seq(0, round(max(d$alt)+0.5,1), 0.5),
- labels = function(x) sprintf("%.1f", x))+
- scale_y_continuous(name = paste(measure_tb, "[xULN]"),
- breaks = seq(0, round(max(d$tb),1), 1),
- labels = function(x) sprintf("%.1f", x)) +
- geom_text(data=plottext,
- aes(x=xpos,y=ypos,
- hjust=hjust,vjust=vjust,
- label=paste(text1, text2)),
- color = 'black', size=3)
- } else {
- d <- data %>%
- select(id_col = id_col,
- measure_col = measure_col,
- value_col = value_col,
- normal_col_high = normal_col_high,
- studyday_col = studyday_col) %>%
- filter(measure_col %in% c(measure_alt, measure_tb)) %>%
- group_by(id_col, measure_col) %>%
- filter(value_col==max(value_col)) %>%
- mutate(peak_val_uln = value_col/normal_col_high) %>%
- unique %>%
- slice(1) %>%
- group_by(id_col) %>%
- unique %>%
- mutate(days_between = abs(studyday_col[1]-studyday_col[2])) %>%
- select(id_col,measure_col, peak_val_uln, days_between) %>%
- unique %>%
- spread(measure_col, peak_val_uln) %>%
- setNames(., c("id","days_between", "alt","tb")) %>%
- na.omit %>%
- ungroup %>%
- mutate(hys_law = 100*sum(alt>3 & tb>2)/n(),
- hyper = 100*sum(alt <3 & tb >2)/n(),
- temple = 100*sum(alt >3 & tb <2)/n(),
- normal = 100*sum(alt <3 & tb <2)/n()
- ) %>%
- mutate_at(vars(hys_law:normal), ~ paste0(format(round(.,1), nsmall=1), "%"))
-
- plottext <- data.frame(
- xpos = c(Inf,-Inf,Inf,-Inf),
- ypos = c(Inf, Inf,-Inf,-Inf),
- hjust = c(1.1,-0.1,1.1,-0.1) ,
- vjust = c(1.3,1.3,-0.3,-0.3),
- text1 = c("Possible Hy's Law Range",
- "Hyperbilirubinemia",
- "Temple's Corollary",
- "Normal Range"),
- text2 = as.character(unique(select(d, hys_law:normal))))
-
- p <- ggplot(data=d, aes(alt, tb)) +
- geom_point(size = 2, alpha=0.7,aes(shape=factor(days_between<=30))) +
- scale_shape_manual(values = c(1, 19), guide=FALSE) +
- theme_bw() +
- geom_hline(yintercept = 2, lty=2, color="darkgray")+
- geom_vline(xintercept = 3, lty=2, color="darkgray") +
- theme(legend.position = "top",
- legend.title=element_blank()) +
- scale_x_continuous(name = paste(measure_alt, "[xULN]"),
- breaks = seq(0, round(max(d$alt)+0.5,1), 0.5),
- labels = function(x) sprintf("%.1f", x))+
- scale_y_continuous(name = paste(measure_tb, "[xULN]"),
- breaks = seq(0, round(max(d$tb),1), 1),
- labels = function(x) sprintf("%.1f", x)) +
- geom_text(data=plottext,
- aes(x=xpos,y=ypos,
- hjust=hjust,vjust=vjust,
- label=paste(text1, text2)),
- color = 'black', size=3)
- }
- return(ggplotly(p) %>% hide_legend())
-}
-
diff --git a/inst/safetyGraphics_app/global.R b/inst/safetyGraphics_app/global.R
deleted file mode 100644
index 8f09a47c..00000000
--- a/inst/safetyGraphics_app/global.R
+++ /dev/null
@@ -1,118 +0,0 @@
-# global.R code for safetyGraphics app
-# - load all required libraries
-# - source module functions
-library(safetyGraphics)
-library(shiny)
-library(shinyWidgets)
-library(shinyjs)
-library(dplyr)
-library(purrr)
-library(stringr)
-library(DT)
-library(haven)
-library(tidyr)
-library(shinybusy)
-
-# remove settings and charts metadata in the workspace to avoid carryover from previous instances of the app.
-if(exists("settingsMetadata", inherits = FALSE)){
- rm("settingsMetadata")
-}
-
-if(exists("chartsMetadata", inherits = FALSE)){
- rm("chartsMetadata")
-}
-
-# use metadata in user settings folder if provided
-if (!is.null(options('sg_chartsMetadata')[[1]]) && options('sg_chartsMetadata')[[1]]){
- chartsMetadata <- options('sg_chartsMetadata_df')[[1]]
-}
-if (!is.null(options('sg_settingsMetadata')[[1]]) && options('sg_settingsMetadata')[[1]]){
- settingsMetadata <- options('sg_settingsMetadata_df')[[1]]
-}
-if (!is.null(options('sg_standardsMetadata')[[1]]) && options('sg_standardsMetadata')[[1]]){
- standardsMetadata <- options('sg_standardsMetadata_df')[[1]]
-}
-
-
-# subset chartsMetadata if user requests it
-if (!is.null(getShinyOption("safetygraphics_charts"))){
- all_charts <- getShinyOption("safetygraphics_charts")
- cat(length(all_charts), "of", nrow(chartsMetadata), "available charts included being loaded. Run `safetyGraphicsApp(charts=NULL)` to use all charts.")
-} else{
- all_charts <- chartsMetadata$chart
-}
-
-metadata_list <- list(chartsMetadata = filter(chartsMetadata, chart %in% all_charts),
- settingsMetadata = settingsMetadata,
- standardsMetadata = standardsMetadata)
-
-# Prepare initial datasets/labels (with info about standards) to be loaded into the app
-# pre-load data into app if requested
-# if(!is.null(getShinyOption("sg_loadData")) && getShinyOption("sg_loadData")){
-# preload_data_list <- list()
-#
-# # names of data in environment
-# dat_names <- ls(pos=1)[sapply(ls(pos=1), function(x) inherits(get(x), "data.frame"))]
-# dat_names <- dat_names[!dat_names %in% c("chartsMetadata","standardsMetdata","settingsMetadata")]
-#
-# preload_data_list$data <- lapply(dat_names, function(x) {get(x)})
-# names(preload_data_list$data) <- dat_names
-#
-# # set all to not currently selected
-# preload_data_list$current <- c(1, rep(0, length(dat_names)-1))
-#
-# # detect standard for all datasets
-# preload_data_list$standard <- lapply(preload_data_list$data, function(x){ detectStandard(x) })
-#
-# # get display name for all datasets
-# preload_data_list$display <- list()
-#
-# for (i in 1:length(dat_names)){
-#
-# temp_standard <- preload_data_list$standard[[i]]$standard
-# standard_label <- ifelse(temp_standard=="adam","AdAM",ifelse(temp_standard=="sdtm","SDTM",temp_standard))
-# if(temp_standard == "none") {
-# preload_data_list$display[[i]] <- HTML(paste0("", names(preload_data_list$data)[i], " - No Standard Detected
"))
-# } else if (preload_data_list$standard[[i]]$details[[temp_standard]]$match == "full") {
-# preload_data_list$display[[i]] <- HTML(paste0("", names(preload_data_list$data)[i], " - ", standard_label, "
"))
-# # If partial data spec match - give the fraction of variables matched
-# } else {
-#
-# valid_count <- preload_data_list$standard[[i]]$details[[temp_standard]]$valid_count
-# total_count <- preload_data_list$standard[[i]]$details[[temp_standard]]$invalid_count + valid_count
-#
-# fraction_cols <- paste0(valid_count, "/" ,total_count)
-#
-# preload_data_list$display[[i]] <- HTML(paste0("", names(preload_data_list$data)[i], " - ", "Partial ",
-# standard_label, " (", fraction_cols, " data settings)", "
"))
-# }
-# }
-# } else { # otherwise use example data
- preload_data_list <- list(labs = list(
- data = list("Example data" = labs),
- current = 1,
- standard = list(list("standard" = "adam", "details" = list("adam"=list("match"="full")))),
- display = list(HTML("Example data - ADaM
"))),
- aes = list(
- data = list("Example data" = aes),
- current = 1,
- standard = list(list("standard" = "sdtm", "details" = list("sdtm"=list("match"="full")))),
- display = list(HTML("Example data - ADaM
"))))
-# }
-
-## source modules
-source('modules/renderSettings/renderSettingsUI.R')
-source('modules/renderSettings/renderSettings.R')
-
-source('modules/renderChart/renderChartUI.R')
-source('modules/renderChart/renderChart.R')
-
-source('modules/renderReports/renderReportsUI.R')
-source('modules/renderReports/renderReports.R')
-
-source('modules/dataUpload/dataUploadUI.R')
-source('modules/dataUpload/dataUpload.R')
-
-
-source('modules/config/configUI.R')
-source('modules/config/config.R')
diff --git a/inst/safetyGraphics_app/modules/config/config.R b/inst/safetyGraphics_app/modules/config/config.R
deleted file mode 100644
index dd3bbc63..00000000
--- a/inst/safetyGraphics_app/modules/config/config.R
+++ /dev/null
@@ -1,73 +0,0 @@
-# Data and settings configuration module - UI code
-#'
-#' Workflow:
-#' (1) For a given domain, this module populates a new selection in the Config dropdown. The selection results in a domain-specific
-#' configuration panel with a Data tab (`dataLoad` module) and a Settings tab (`renderSettings` module).
-#' (2) The initial user-selected dataset and its associated settings and validation statuses are passed to the settings module.
-#' (3) The settings module passes the configured settings, selected charts, and final validation status back to the config module server.
-#' (4) A list of the selected/configured data and settings, along with validation statuses are passed back to the main app.
-#'
-#' @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 metadata A list configured in `global.R` containing the charts, settings, and standards metadata:
-#' `metadata_list <- list(chartsMetadata = chartsMetadata,
-#' settingsMetadata = settingsMetadata,
-#' standardsMetadata = standardsMetadata)`
-#' @param domain Which data domain should the module be customized for? Example: "labs"
-#' @param preload_data_list Named list of data.frames configured in `global.R` to be pre-loaded into the app.
-#' Named according to data domain. Contains information about associated data standard and display specifications.
-#'
-#' @return List of reactives containing the user-selected dataset (a data.frame),
-#' customized settings (a list), and selected charts/validation statuses (a named logical vector).
-config <- function(input, output, session, metadata, domain, preload_data_list){
-
- ns <- session$ns
-
- # filter the metadata
- metadata <- metadata
- settingsMetadata <- filter(metadata$settingsMetadata, domain==!!domain)
- chartsMetadata <- filter(metadata$chartsMetadata, domain==!!domain)
- standardsMetadata <- filter(metadata$standardsMetadata, domain==!!domain)
- domain_charts <- chartsMetadata$chart
- names(domain_charts) <- chartsMetadata$label
-
-
- ##############################################################
- # initialize dataUpload module
- #
- # returns selected dataset, settings, and validation status
- ##############################################################
- dataUpload_out <- callModule(dataUpload, "datatab", domain=domain,
- preload_data_list = preload_data_list[[domain]])
-
- ##############################################################
- # 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.
- #
- # 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()),
- metadata=settingsMetadata,
- charts = domain_charts
- )
-
-
-
- return(list(data = reactive(dataUpload_out$data_selected()),
- settings = reactive(settings_new$settings()),
- charts = reactive(settings_new$charts())))
-
-}
-
diff --git a/inst/safetyGraphics_app/modules/config/configUI.R b/inst/safetyGraphics_app/modules/config/configUI.R
deleted file mode 100644
index 09e25b99..00000000
--- a/inst/safetyGraphics_app/modules/config/configUI.R
+++ /dev/null
@@ -1,26 +0,0 @@
-# Data and settings configuration module - UI code
-#'
-#' @param id The module-specific ID that will get pre-pended to all element IDs
-#'
-#' @return The UI for the data and settings configuration. Generated once per domain and nested within the "Config" dropdown. Contains
-#' `dataUpload` and `renderSettings` module UIs nested within.
-configUI <- function(id){
-
- ns <- NS(id)
- tagList(
- tabsetPanel(
- tabPanel(
- title = "Data",
- dataUploadUI(ns("datatab"))
- ) ,
- tabPanel(
- title = "Settings",
- fluidPage(
- renderSettingsUI(ns("settingsUI"))
- )
- )
- )
-
- )
-}
-
diff --git a/inst/safetyGraphics_app/modules/dataUpload/dataUpload.R b/inst/safetyGraphics_app/modules/dataUpload/dataUpload.R
deleted file mode 100644
index 2980953a..00000000
--- a/inst/safetyGraphics_app/modules/dataUpload/dataUpload.R
+++ /dev/null
@@ -1,207 +0,0 @@
-#' Data upload module - server code
-#'
-#' This module creates the Data tab for the Shiny app.
-#'
-#' Workflow:
-#' (1) A reactiveValues() list is created with example dataset as first entry.
-#' The following information is included in list:
-#' - dataset and name
-#' - current (whether the dataset came from most recent upload)
-#' - data standard / quality of match
-#' (2) Upon user data upload:
-#' - reactiveValues list is updated with information about new data.
-#' - radio buttons are updated with new data choices
-#' (3) When a new dataset is selected, the data preview output is invalidated
-#' (4) When a new dataset is selected OR the standard changes (since these don't update at the same time), the
-#' the settings object ("settings()") is invalidated.
-#' (5) When a new dataset is selected OR the settings object is updated, the settings validation ("status()") is
-#' invalidated.
-#'
-#' @param input Input objects from module namespace
-#' @param output Output objects from module namespace
-#' @param session An environment that can be used to access information and functionality relating to the session
-#'
-#' @return A list of reactive values, including:
-#' \itemize{
-#' \item{"data_selected"}{A data frame selected by the user}
-#' \item{"settings"}{Result from generateSettings() for data_selected}
-#' \item{"status"}{Result from validateSettings() for data_selected and settings}
-#'
-dataUpload <- function(input, output, session, domain, preload_data_list){
-
- ns <- session$ns
-
- # initiate reactive values - list of uploaded data files
- # standard to imitate output of detectStandard.R
- dd <- reactiveValues(data = preload_data_list$data,
- current = preload_data_list$current,
- standard = preload_data_list$standard)
-
- output$data_select <- renderUI({
- radioButtons(ns("select_file"),"Select file for safetyGraphics charts",
- choiceNames = preload_data_list$display,
- choiceValues = names(preload_data_list$data))
- })
-
- # ensure outputs update upon app startup
- outputOptions(output, "data_select", suspendWhenHidden = FALSE)
-
- # modify reactive values when data is uploaded
- observeEvent(input$datafile,{
-
- data_list <- list()
-
- ## data list
- for (i in 1:nrow(input$datafile)){
- if (length(grep(".csv", input$datafile$name[i], ignore.case = TRUE)) > 0){
- data_list[[i]] <- data.frame(read.csv(input$datafile$datapath[i], na.strings=NA))
- }else if(length(grep(".sas7bdat", input$datafile$name[i], ignore.case = TRUE)) > 0){
- data_list[[i]] <- haven::read_sas(input$datafile$datapath[i])
- }else{
- data_list[[i]] <- NULL
- }
- }
- # names
- names(data_list) <- input$datafile$name
-
- # append to existing reactiveValues list
- dd$data <- c(dd$data, data_list)
-
- # set dd$current to FALSE for previous & TRUE for current uploads
- dd$current <- c(rep(FALSE, length(dd$current)), rep(TRUE, length(data_list)))
-
- # run detectStandard on new data and save to dd$standard
-
- standard_list <- lapply(data_list, function(x){ detectStandard(x) })
-
- #standard_list <- lapply(data_list, function(x){ detectStandard(x)$standard })
-
- dd$standard <- c(dd$standard, standard_list)
-
- })
-
-
- ### make a reactive combining dd$data & standard
- data_choices <- reactive({
-
- req(dd$data)
- req(dd$standard)
-
- choices <- list()
- for (i in 1:length(dd$data)){
- choices[[i]] <- names(dd$data)[i]
- }
-
- for (i in 1:length(dd$data)){
-
- temp_standard <- dd$standard[[i]]$standard
- standard_label <- ifelse(temp_standard=="adam","AdAM",ifelse(temp_standard=="sdtm","SDTM",temp_standard))
- if(temp_standard == "none") {
- names(choices)[i] <- paste0("", names(dd$data)[i], " - No Standard Detected
")
- } else if (dd$standard[[i]]$details[[temp_standard]]$match == "full") {
- names(choices)[i] <- paste0("", names(dd$data)[i], " - ", standard_label, "
")
- # If partial data spec match - give the fraction of variables matched
- } else {
-
- valid_count <- dd$standard[[i]]$details[[temp_standard]]$valid_count
- total_count <- dd$standard[[i]]$details[[temp_standard]]$invalid_count + valid_count
-
- fraction_cols <- paste0(valid_count, "/" ,total_count)
-
- names(choices)[i] <- paste0("", names(dd$data)[i], " - ", "Partial ",
- standard_label, " (", fraction_cols, " data settings)", "
")
- }
- }
- return(choices)
- })
-
-
-
- # update radio buttons to display dataset names and standards for selection
- observeEvent(input$datafile, {
- req(data_choices())
- vals <- data_choices()
- names(vals) <- NULL
- names <- lapply(names(data_choices()), HTML)
-
- prev_sel <- lapply(reactiveValuesToList(input), unclass)$select_file # retain previous selection
-
- updateRadioButtons(session, "select_file",
- choiceNames = names,
- choiceValues = vals,
- selected = prev_sel)
-
- })
-
-
- # get selected dataset when selection changes
- data_selected <- eventReactive(input$select_file, {
- isolate({index <- which(names(dd$data)==input$select_file)[1]})
- dd$data[[index]]
- })
-
- # upon a dataset being uploaded and selected, generate data preview
- output$datapreview_header <- renderUI({
- data_selected()
- isolate(data_name <- input$select_file)
- h3(paste("Data Preview for", data_name))
- })
-
- output$data_preview <- DT::renderDataTable({
- DT::datatable(data = data_selected(),
- caption = isolate(input$select_file),
- rownames = FALSE,
- style="bootstrap",
- class="compact",
- extensions = "Scroller", options = list(scrollY=400, scrollX=TRUE))
- })
-
-
- # upon a dataset being selected, grab its standard
- standard <- eventReactive(data_selected(), {
- index <- which(names(dd$data)==input$select_file)[1]
- dd$standard[[index]]
- })
-
-
- # upon a dataset being selected, use generateSettings() to produce a settings obj
- settings <- eventReactive(c(data_selected(), standard()), {
-
- current_standard <- standard()$standard
-
- if (! current_standard=="none"){
- partial <- ifelse(standard()$details[[current_standard]]$match == "partial", TRUE, FALSE)
-
- if (partial) {
- partial_keys <- standard()$details[[current_standard]]$checks %>%
- filter(valid==TRUE) %>%
- select(text_key) %>%
- pull()
-
- generateSettings(standard=current_standard, partial=partial, partial_keys = partial_keys)
-
- } else {
- generateSettings(standard=current_standard)
- }
- } else {
- generateSettings(standard=current_standard)
- }
- })
-
-
- # run validateSettings(data, standard, settings) and return a status
- status <- reactive({
- req(data_selected())
- req(settings())
- validateSettings(data_selected(),
- settings())
- })
-
- exportTestValues(status = { status() })
-
- ### return selected data, settings, and status to server
- return(list(data_selected = reactive(data_selected()),
- settings = reactive(settings()),
- status = reactive(status())))
-
-}
diff --git a/inst/safetyGraphics_app/modules/dataUpload/dataUploadUI.R b/inst/safetyGraphics_app/modules/dataUpload/dataUploadUI.R
deleted file mode 100644
index 9d2e813b..00000000
--- a/inst/safetyGraphics_app/modules/dataUpload/dataUploadUI.R
+++ /dev/null
@@ -1,44 +0,0 @@
-#' Data upload module - UI code
-#'
-#' This module creates the Data tab for the Shiny app.
-#'
-#' The UI contains:
-#' - a file upload control
-#' - radio buttons for selecting from the available datasets
-#' - raw data preview.
-#'
-#' @param id The module-specific ID that will get pre-pended to all element IDs
-#'
-#' @return The UI for the Data tab
-#'
-dataUploadUI <- function(id){
-
- ns <- NS(id)
-
- tagList(
- fluidRow(
- column(3,
- wellPanel(
- h3("Data upload"),
- fileInput(ns("datafile"), "Upload a csv or sas7bdat file",accept = c(".sas7bdat", ".csv"), multiple = TRUE),
- uiOutput(ns("data_select"))
- # radioButtons(ns("select_file"),"Select file for safetyGraphics charts",
- # choiceNames = preload_data_list$display,
- # choiceValues = names(preload_data_list$data))
- # radioButtons(ns("select_file"),"Select file for safetyGraphics charts",
- # choiceNames = list(HTML("Example data - ADaM
")),
- # choiceValues = "Example data")
- )
- ),
- column(6,
- fluidRow(
- wellPanel(
- uiOutput(ns("datapreview_header")),
- div(DT::dataTableOutput(ns("data_preview")), style = "font-size: 75%")
- )
- )
- )
- )
- )
-
-}
diff --git a/inst/safetyGraphics_app/modules/renderChart/renderChart.R b/inst/safetyGraphics_app/modules/renderChart/renderChart.R
deleted file mode 100644
index c54890e0..00000000
--- a/inst/safetyGraphics_app/modules/renderChart/renderChart.R
+++ /dev/null
@@ -1,79 +0,0 @@
-#' Render eDISH chart - server code
-#'
-#' This module creates the Chart tab for the Shiny app, which contains the interactive eDISH graphic.
-#'
-#' Workflow:
-#' (1) A change in `data`, `settings`, or `valid` invalidates the eDISH chart output
-#' (2) Upon a change in `valid`, the export chart functionality is conditionally made available or unavailable to user
-#' (3) If "export chart" button is pressed, data and settings are passed to the parameterized report, knitted using
-#' Rmarkdown, and downloaded to user computer.
-#'
-#' @param input Input objects from module namespace
-#' @param output Output objects from module namespace
-#' @param session An environment that can be used to access information and functionality relating to the session
-#' @param data A data frame [REACTIVE]
-#' @param settings list of settings arguments for chart [REACTIVE]
-#' @param chart name of chart to be rendered [STRING]
-#' @param type type of chart (e.g. "htmlwidget")
-#' @param valid A logical indicating whether data/settings combination is valid for chart [REACTIVE]
-
-renderChart <- function(input, output, session, data, settings, valid, chart, type, width){
-
- ns <- session$ns
-
- # function for chart
- #chart_fun <- match.fun(chart)
- # function for shiny output
- #output_fun <- match.fun(paste0("output_", chart))
- # function for shiny render
- #render_fun <- match.fun(paste0("render_", chart))
- # id for chart
- chart_id <- paste0("chart_", chart)
-
-
- if (type=="module"){
-
- chartCode <- system.file("custom", type, paste0(chart, ".R"), package = "safetyGraphics")
- source(chartCode)
- chart_ui <- match.fun(paste0(chart, "_UI"))
- chart_server <- match.fun(chart)
-
- output$chart <- renderUI({
- chart_ui(ns(chart_id))
- })
-
- callModule(chart_server, chart_id)
-
- } else {
- ## code to dynamically generate the output location
- output$chart <- renderUI({
- if (type=="htmlwidget"){
- output_chartRenderer(ns(chart_id))
- } else if (type=="static") {
- plotOutput(ns(chart_id), width = paste0(width, "px"))
- }else if (type=="plotly") {
- plotlyOutput(ns(chart_id), width = paste0(width, "px"))
- } else if (type=="module"){
- hepexplorer_mod_UI(ns(chart_id))
- }
- })
-
- ## code to render widget and fill in the output location
- if (type=="htmlwidget"){
- render_fun <- match.fun("render_chartRenderer")
- } else if (type=="static"){
- render_fun <- match.fun("renderPlot")
- } else if (type=="plotly"){
- render_fun <- match.fun("renderPlotly")
- }
-
- output[[chart_id]] <- render_fun({
- req(data())
- req(settings())
- #trimmed_data<-trim_data()
- chartRenderer(data = data(), settings = settings(), chart = chart, debug_js=TRUE)
- })
- }
-
-
-}
diff --git a/inst/safetyGraphics_app/modules/renderChart/renderChartUI.R b/inst/safetyGraphics_app/modules/renderChart/renderChartUI.R
deleted file mode 100644
index 634c0764..00000000
--- a/inst/safetyGraphics_app/modules/renderChart/renderChartUI.R
+++ /dev/null
@@ -1,17 +0,0 @@
-#' Render eDISH chart - UI code
-#'
-#' This module creates the Chart tab for the Shiny app, which contains the interactive eDISH graphic.
-
-#' @param id The module-specific ID that will get pre-pended to all element IDs
-#'
-#' @return The UI for the Chart tab
-#'
-renderChartUI <- function(id){
-
- ns <- NS(id)
-
- tagList(
- uiOutput(ns("chart"))
- )
- #eDISHOutput(ns("chart"))
-}
diff --git a/inst/safetyGraphics_app/modules/renderReports/renderReports.R b/inst/safetyGraphics_app/modules/renderReports/renderReports.R
deleted file mode 100644
index dc73815c..00000000
--- a/inst/safetyGraphics_app/modules/renderReports/renderReports.R
+++ /dev/null
@@ -1,100 +0,0 @@
-#' Render Reports Tab - server code
-#'
-#' This module creates the Reports tab for the Shiny app, which contains the interactive eDISH graphic.
-#'
-#' Workflow:
-#' (1) A change in `charts` invalidates the report options
-#' (2) Upon a change in `charts`, the chart list for export is updated
-#' (3) If "Export Chart(s)" button is pressed, data, settings, and the selected charts 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 configVals The output from the config modules - user selected data, settings, and charts - captured in a reactiveValues object. One sublist per domain.[REACTIVE VALUES]
-#' @param chartsMetadata A filtered instance of the original charts metadata, containing only details needed for the reports module. [REACTIVE]
-
-renderReports <- function(input, output, session, configVals, chartsMetadata){
-
- ns <- session$ns
-
-
- data_list <- reactive({
- out <- list()
- for (i in names(configVals)){
- data <- configVals[[i]]$data()
- out <- c(out, list(data))
- }
- names(out) <- names(configVals)
- return(out)
- })
-
-
- settings_list <- reactive({
- out <- list()
- for (i in names(configVals)){
- settings <- configVals[[i]]$settings()
- out <- c(out, list(settings))
- }
- names(out) <- names(configVals)
- return(out)
- })
-
- observeEvent(chartsMetadata(), {
-
- charts_vec <- chartsMetadata()$chart
- names(charts_vec) <- chartsMetadata()$label
- checkboxes <- checkboxGroupInput(ns('chk'), choices = charts_vec, selected = charts_vec, label = "Select Charts for Export")
-
- output$checkboxes <- renderUI(checkboxes)
-
- }, ignoreNULL=FALSE)
-
-
- # subset metadata based on user selections
- charts_sub <- reactive({
- req(input$chk)
- req(chartsMetadata())
- md <- chartsMetadata()
- md[md$chart %in% input$chk,]
- })
-
- # insert export chart(s) button if there are charts selected
-
- observeEvent(chartsMetadata(), {
- removeUI(selector = paste0("#", ns("download")))
- if (!is.null(chartsMetadata())){
- insertUI (
- selector = "div.reportPanel",
- where = "afterEnd",
- ui = div(id=ns("download"), # give the container div an id for easy removal
- style="float: left;",
- span( downloadButton(ns("reportDL"), "Export Chart(s)")) )
- )
- }
- }, ignoreNULL=FALSE)
-
-
- # Set up report generation on download button click
- output$reportDL <- downloadHandler(
- filename = "safetyGraphicsReport.html",
- content = function(file) {
- # Copy the report file to a temporary directory before processing it, in case we don't
- # have write permissions to the current working dir (which can happen when deployed).
- templateReport <- system.file("safetyGraphics_app/modules/renderReports","safetyGraphicsReport.Rmd", package = "safetyGraphics")
- tempReport <- file.path(tempdir(), "report.Rmd")
- file.copy(templateReport, tempReport, overwrite = TRUE)
- params <- list(data = data_list(),
- settings = settings_list(),
- charts=charts_sub() )
-
- rmarkdown::render(tempReport,
- output_file = file,
- params = params, ## pass in params
- envir = new.env(parent = globalenv()) ## eval in child of global env
- )
- }
- )
-
-
-}
diff --git a/inst/safetyGraphics_app/modules/renderReports/renderReportsUI.R b/inst/safetyGraphics_app/modules/renderReports/renderReportsUI.R
deleted file mode 100644
index 2c04de81..00000000
--- a/inst/safetyGraphics_app/modules/renderReports/renderReportsUI.R
+++ /dev/null
@@ -1,28 +0,0 @@
-#' Render Reports Tab - UI code
-#'
-#' This module creates the Reports tab for the Shiny app.
-
-#' @param id The module-specific ID that will get pre-pended to all element IDs
-#'
-#' @return The UI for the Reports tab
-#'
-renderReportsUI <- function(id){
-
- ns <- NS(id)
-
- fluidPage(
- fluidRow(
- column(10,
- wellPanel(
- class="reportPanel",
- h3(
- "Charts"
- ),
- uiOutput(ns("checkboxes"))
- )
-
- )
- )
- )
-
-}
diff --git a/inst/safetyGraphics_app/modules/renderReports/safetyGraphicsReport.Rmd b/inst/safetyGraphics_app/modules/renderReports/safetyGraphicsReport.Rmd
deleted file mode 100644
index 2b300f86..00000000
--- a/inst/safetyGraphics_app/modules/renderReports/safetyGraphicsReport.Rmd
+++ /dev/null
@@ -1,118 +0,0 @@
----
-output:
- html_document
-
-params:
- data: NA
- settings: NA
- charts: NA
-
----
-
-## Customized Interactive Safety Graphics {.tabset .tabset-fade}
-
-```{r results='asis', echo = FALSE, message=FALSE, warning = FALSE}
-library(safetyGraphics)
-library(knitr)
-
-subchunkify_title <- function(chart) {
- sub_chunk <- paste0("### ",chart,"\n")
- cat(sub_chunk)
-}
-
-subchunkify_chart <- function(chart,data, settings, fig_height=15, fig_width=12) {
- g_deparsed <- paste0(deparse(
- function() {chartRenderer(data = data,
- settings = settings, chart=chart)}
- ), collapse = '')
-
- sub_chunk <- paste0("
- `","``{r sub_chunk_", floor(runif(1) * 10000), ", fig.height=",
- fig_height, ", fig.width=", fig_width, ", echo=FALSE, message=FALSE, warning=FALSE}",
- "\n(",
- g_deparsed
- , ")()",
- "\n`","``
- ",'\n')
-
- cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
-}
-
-md <- data.frame(params$charts)
-for (chart in md$chart) {
- domain <- md[md$chart==chart,]$domain
- chart_settings <- trimSettings(settings=params$settings[[domain]], charts=chart)
- subchunkify_title(md[md$chart==chart,]$label)
- subchunkify_chart(chart, params$data[[domain]], chart_settings)
-}
-
-
-```
-
-
-
-### Info
-
-#### Background
-The safetyGraphics package provides a framework for evaluation of clinical trial safety in R. Examples and additional documentation are available [here](https://github.com/ASA-DIA-InteractiveSafetyGraphics/safetyGraphics).
-
-safetyGraphics is an open source project built using standard web technology and will run in any modern web browser. The displays created are all dynamically linked to raw safety data which allows the tool to work with data from any safety system. The tool was originally created using Javascript/D3, but has been extended to an R tool as well using HTML Widgets.
-
-
-
-#### Code to Reproduce Charts
-
-Use the script below to load the safetyGraphics package and your data. Make sure to update the second line to point at your data.
-
-```{r, comment=NA, echo=FALSE}
-lib_code <- quote(library(safetyGraphics))
-lib_code
-
-writeLines("path <- 'path_to_data'") ### <-- Update this!
-
-domains <- unique(data.frame(params$charts)$domain)
-for(d in domains){
- writeLines(paste0("my_", d, "_data <- read.csv(file.path(path, '", d, "_data.csv'))"))
-}
-
-```
-
-```{r results='asis', echo = FALSE, message=FALSE, warning = FALSE}
-
-subchunkify_header <- function(chart){
- g_deparsed <- paste0("**Code to render ",chart," chart**\n")
-
- cat(g_deparsed)
-}
-
-subchunkify_code <- function(chart,settings, domain){
- dat <- sym(paste0("my_", domain, "_data"))
- g_deparsed <- paste0(deparse(
- function() {
- bquote(chartRenderer(data = .(dat),
- settings = .(settings), chart=.(chart)))
- }
- ), collapse = '')
-
- sub_chunk <- paste0("
- `","``{r sub_chunk_", floor(runif(1) * 10000),", comment = NA",", echo=FALSE}",
- "\n(",
- g_deparsed
- , ")()",
- "\n`","``
- ",'\n')
-
- cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
-}
-
-md <- data.frame(params$charts)
-for (chart in md$chart) {
- domain <- md[md$chart==chart,]$domain
- chart_settings <- trimSettings(settings=params$settings[[domain]], charts=chart)
- subchunkify_header(md[md$chart==chart,]$label)
- subchunkify_code(chart, chart_settings, domain)
-}
-
-
-```
-
diff --git a/inst/safetyGraphics_app/modules/renderSettings/renderSettings.R b/inst/safetyGraphics_app/modules/renderSettings/renderSettings.R
deleted file mode 100644
index 099363c7..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/renderSettings.R
+++ /dev/null
@@ -1,354 +0,0 @@
-# Functions to include
-source("modules/renderSettings/util/createSettingsSection.R")
-source("modules/renderSettings/util/createSettingLabel.R")
-source("modules/renderSettings/util/createControl.R")
-source("modules/renderSettings/util/createSettingsUI.R")
-source("modules/renderSettings/util/updateSettingStatus.R")
-
-#' Render Settings module - Server code
-#'
-#' This module creates the Settings tab for the Shiny app.
-#'
-#' Workflow:
-#' (1) Reactive input_names() contains names of settings related to selected charts. When a user changes
-#' chart selections, input_names() is invalidated.
-#' (2) A change in input_names(), `data`, or `settings` invalidates the following:
-#' - renderUI associated with data mapping settings
-#' - renderUI associated with measure settings
-#' - renderUI associated with appearance settings
-#' (3) These renderUI's call upon the createSettingsUI() function and will update
-#' even when settings tab not in view. They will create and populate the UI for all related settings.
-#' (4) Field-level inputs are updated upon any of the following events:
-#' - a change in selected data
-#' - change in selected chart(s)
-#' - change in column-level input selection
-#' update includes:
-#' - Deactivate/activate field-level selector based on whether column-level input has been selected
-#' - Data choices for field-level inputs based on selected column-level input
-#' (5) A reactive representing the new settings object (settings_new()) is created based on UI selections. This object is invalidated
-#' when ANY input changes.
-#' (6) A reactive representing the new data/settings validation (status_new()) is created based on data and updated settings object.
-#' A change in data OR updated settings object invalidated this reactive.
-#' (7) Upon a change in the new validation (status_new() and derived status_df()), updated status messages are
-#' printed on UI by calling updateSettingStatus(). ALL messages are re-printed at once.
-#'
-#' @param input Input objects from module namespace
-#' @param output Output objects from module namespace
-#' @param session An environment that can be used to access information and functionality relating to the session
-#' @param data A user-selected data frame from the dataLoad module [REACTIVE]
-#' @param settings Settings object that corresponds to data's standard - result of generateSettings(). [REACTIVE]
-#' @param status A list describing the validation state for data/settings - result of validateSettings(). [REACTIVE]
-#' @param metadata A data frame of settings metadata specific to the current domain.
-#' @param charts A named vector of domain-specific charts, where the name is the chart display label, and the value is the chart name used in the code.
-#'
-#' @return A list of reactive values, including:
-#' \itemize{
-#' \item{"settings"}{Upadted settings object based on UI/user selections}
-#' \item{"status"}{Result from validateSettings() for originally selected data + updated settings object}
-#' \item{"charts"}{A named logical vector, with names corresponding to the user-selected charts, and values corresponding to the validation statuses.}
-#'
-renderSettings <- function(input, output, session, data, settings, status, metadata, charts){
- ns <- session$ns
-
-
- output$charts_wrap_ui <- renderUI({
- checkboxGroupButtons(
- ns("charts"),
- label = NULL,
- choices = charts,
- selected = charts,
- checkIcon = list(
- yes = icon("ok-circle", lib = "glyphicon"),
- no = icon("remove-circle",lib = "glyphicon")
- ),
- status="primary"
- )
- })
-
- #List of all inputs
- # Null if no charts are selected
- input_names <- reactive({
- if(!is.null(input$charts)){
- safetyGraphics:::getSettingsMetadata(charts=input$charts, cols="text_key", metadata=metadata) %>% unique
- } else{
- NULL
- }
-
- })
-
-
- ######################################################################
- # create settings UI
- # - chart selection -> gather all necessary UI elements
- # - create elements based on metadata file
- # - populate using data/settings
- ######################################################################
-
- output$data_mapping_ui <- renderUI({
- charts <- isolate(input$charts)
- tagList(
- createSettingsUI(
- data=data(),
- settings = settings(),
- setting_cat_val = "data",
- charts=charts,
- metadata=metadata,
- ns=ns
- )
- )
- })
-
-
- output$measure_settings_ui <- renderUI({
- charts <- isolate(input$charts)
- tagList(
- createSettingsUI(
- data=data(),
- settings = settings(),
- setting_cat_val = "measure",
- charts=charts,
- metadata=metadata,
- ns=ns
- )
- )
- })
-
- output$appearance_settings_ui <- renderUI({
- charts <- isolate(input$charts)
- tagList(
- createSettingsUI(
- data=data(),
- settings = settings(),
- setting_cat_val = "appearance",
- charts=charts,
- metadata=metadata,
- ns=ns
- )
- )
- })
-
- ######### Hide Settings that are not relevant to selected charts ########
- observeEvent(input$charts,{
-
- input_names <- isolate(input_names())
-
- # Make sure all relevant settings are showing
- 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"),
- metadata=metadata
- )
- # Identify which settings in input_names() are not relevant
- 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
-
- # 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)
-
- ######################################################################
- # 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,
- metadata = metadata
- )
- 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,
- metadata = metadata
- )
-
- ### SET UP CHOICES/PLACEHOLDERS FOR SELECT INPUT UPDATES
- # If it is the default column - populate standards
- if(input[[col]] == isolate(settings()[[col]]) && !is.null(isolate(settings()[[col]]))) {
- choices <- unique(data()[,input[[col]]])
- placeholder <- list (onInitialize = I('function() { }'))
-
- # If it's another column display placeholder message and set to empty
- } else if(input[[col]] %in% colnames(data())) {
- choices <- unique(data()[,input[[col]]])
- placeholder <- list(
- placeholder = "Please select a value",
- onInitialize = I('function() {
- this.setValue("");}')
- )
- # If empty display different placeholder message
- } else {
- choices <- NULL
- placeholder <- list(
- placeholder = paste0("Please select a ", getSettingsMetadata(col="label", text_key=col,
- metadata = metadata )),
- onInitialize = I('function() {
- this.setValue("");}')
- )
- }
-
- # update selectInput for each field value
- for (key in field_keys){
- # Toggle field-level inputs:
- # ON - if column-level input is selected)
- # OFF - if column-level input is not yet selected
- toggleState(id = key, condition = !input[[col]]=="")
-
- # if specified in original settings object - append value to choices
- if(input[[col]] == isolate(settings()[[col]]) && !is.null(isolate(settings()[[col]]))) {
- setting_key <- as.list(strsplit(key,"\\-\\-"))
- setting_value <- safetyGraphics:::getSettingValue(key=setting_key, settings= isolate(settings()))
- choices <- unique(c(setting_value, choices))
- }
- if (is.null(names(choices))){
- names(choices) <- choices
- }
- updateSelectizeInput(
- session,
- inputId = key,
- choices = choices,
- options = placeholder
- ) #update SelectizeInput
- } #for loop
- } #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({
-
- getValues <- function(x){
- if (is.null(input[[x]])){
- return(NULL)
- } else{
- return(input[[x]])
- }
- }
-
- req(input_names())
- keys <- input_names()
- values<- keys %>% map(~getValues(.x))
-
- 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{
- settings<- generateSettings(charts=input$charts)
- }
-
- return(settings)
- })
-
-
- ######################################################################
- # validate new settings
- # the validation is run every time there is a change in data and/or settings.
- #
- ######################################################################
-
- status_new <- reactive({
- req(data())
- req(settings_new())
-
- name <- rev(isolate(input_names()))[1]
- settings_new <- settings_new()
- charts <- isolate(input$charts)
- out<-validateSettings(data(), settings_new, charts=charts)
-
- return(out)
- })
-
-
- ######################################################################
- # Setting validation status information
- ######################################################################
- status_df <- reactive({
- req(status_new())
- status_new()[["checks"]] %>%
- group_by(text_key) %>%
- mutate(num_fail = sum(valid==FALSE)) %>%
- mutate(icon = ifelse(num_fail==0, "",""))%>%
- mutate(
- message_long = paste(message, collapse = " ") %>% trimws(),
- message_short = case_when(
- num_fail==0 ~ "OK",
- num_fail==1 ~ "1 failed check.",
- TRUE ~ paste(num_fail, "failed checks.")
- )
- ) %>%
- select(text_key, icon, message_long, message_short, num_fail) %>%
- unique
- })
-
- # for shiny tests
- exportTestValues(status_df = { status_df() })
-
- ######################################################################
- # print validation messages
- ######################################################################
- 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"]
- icon <- status_df()[status_df()$text_key==key, "icon"]
- updateSettingStatus(
- ns=ns,
- key=key,
- status_short=status_short,
- status_long=status_long,
- icon=icon
- )
- }
- }
- })
-
- ### return updated settings and status to global env.
- return(
- list(
- settings = reactive(settings_new()),
- status = reactive(status_new()),
- charts = reactive(status_new()$charts)
- )
- )
-}
diff --git a/inst/safetyGraphics_app/modules/renderSettings/renderSettingsUI.R b/inst/safetyGraphics_app/modules/renderSettings/renderSettingsUI.R
deleted file mode 100644
index 4e1d1eef..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/renderSettingsUI.R
+++ /dev/null
@@ -1,11 +0,0 @@
-
-renderSettingsUI <- function(id){
- ns <- NS(id)
- #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/safetyGraphics_app/modules/renderSettings/util/createControl.R b/inst/safetyGraphics_app/modules/renderSettings/util/createControl.R
deleted file mode 100644
index 1533a4e7..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/util/createControl.R
+++ /dev/null
@@ -1,123 +0,0 @@
-#' Create setting control
-#'
-#' Workflow:
-#' (1) Get setting label and description from metadata
-#' (2) Get setting value from settings object
-#' (3) Get choices and placeholder text for the selectors based on metadata, data, and settings
-#' (4) Create HTML code for the selector based on the following metadata:
-#' - whether the option is a column or field-level input
-#' - data type of the setting (e.g. character/numeric/logical, vector of length 1 vs >1)
-#' - label, description, choices, selected value, placeholder text
-#'
-#' @param key A character key representing the setting of interest
-#' @param metadata Metadata data frame to be queried for information about the setting
-#' @param data A data frame to be used to populate control options
-#' @param settings A settings list to be used to populate control options
-#' @param ns The namespace of the current module
-#'
-#'@import map_chr from purrr
-#'
-#' @return HTML code for the div containing the setting of interest
-createControl <- function(key, metadata, data, settings, ns){
-
- sm_key <- filter(metadata, text_key==key)
- ctl_id <- paste0("ctl_", key)
- tt_msg <- paste0("tt_msg_", key)
- msg <- paste0("msg_", key)
-
- data_choices <- names(data)
-
- # function to get labels
- getVarLabel <- function(var){
- lab <- attributes(var)$label # SAS
- if (is.null(lab)){
- return("")
- } else {
- return(paste0(" [",lab,"]"))
- }
- }
-
- names(data_choices) <- paste(data_choices, map_chr(data, ~getVarLabel(.)))
-
-
- ## of the selected charts, which ones are relevant to the given setting?
- charts_rel <- select(sm_key, starts_with("chart_")) %>%
- gather(chart, val) %>%
- filter(val) %>%
- mutate(chart = stringr::str_remove(chart, "chart_")) %>%
- left_join(chartsMetadata, by="chart") %>%
- pull(label)
-
-
-
- ### get metadata for the input
- setting_key <- as.list(strsplit(key,"\\-\\-"))
- setting_value <- safetyGraphics:::getSettingValue(key=setting_key, settings=settings)
- setting_label <- createSettingLabel(key, metadata=metadata)
- setting_description <- getSettingsMetadata(text_keys=key, cols="description", metadata=metadata)
- setting_required <- ifelse(getSettingsMetadata(text_keys=key, cols="setting_required", metadata=metadata),"\nSetting Required","\nSetting Optional")
-
- ### if a field-level input, get metadata about the parent column-level input
- field_column <- NULL
- field_column_label <- NULL
-
- if (!is.null(sm_key$field_column_key)){
- field_column <- safetyGraphics:::getSettingValue(key=list(sm_key$field_column_key), settings=settings)
- field_column_label <- getSettingsMetadata(text_key = sm_key$field_column_key, cols = "label", metadata=metadata)
- }
-
- ### get the choices for the selectors
- value <- NULL
- choices <- NULL
- placeholder <- NULL
-
- if(sm_key$column_mapping==TRUE & is.null(setting_value)){ #column mapping - no value specified
- choices <- data_choices
- placeholder <- list(onInitialize = I('function() {this.setValue("");}'))
- } else if(sm_key$column_mapping==TRUE & !is.null(setting_value)) { #column mapping - value specified
- # force the variable that's specified in settings to be first option
- # combine vectors without losing the labels
- choices <- c(data_choices[data_choices %in% setting_value], data_choices[! data_choices %in% setting_value])
- placeholder <- list (onInitialize = I('function() { }'))
- } else if (sm_key$field_mapping==TRUE & is.null(field_column)){ ## if there is NOT a column specified in settings
- placeholder <- list(
- placeholder = paste0("Please select a ", field_column_label),
- onInitialize = I('function() {
- this.setValue("");}')
- )
- } else if (sm_key$field_mapping==TRUE & !is.null(field_column)){ ## if there is NOT a column specified in settings
- choices <- unique(c(setting_value, 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
- }
-
- ### create code for the UI
- multiple <- (sm_key$setting_type=="vector")
-
- if (sm_key$column_mapping==TRUE | sm_key$field_mapping==TRUE){
- input <- selectizeInput(inputId = ns(key), label = NULL, choices = choices, options = placeholder, multiple = multiple)
- } else if (sm_key$setting_type=="vector"){
- input <- selectizeInput(inputId = ns(key), label = NULL, choices = choices, selected = choices, multiple = TRUE)
- } else if (sm_key$setting_type=="numeric"){
- input <- sliderInput(inputId = ns(key), label = NULL, value=setting_value, min=0, max=50)
- } else if (sm_key$setting_type=="logical"){
- input <- checkboxInput(inputId = ns(key), label = NULL, value=setting_value)
- } else if (sm_key$setting_type=="character"){
- input <-textAreaInput(inputId = ns(key), label = NULL, value = setting_value)
- }
-
- div(
- class="control-wrap",
- id=ns(ctl_id),
- span(title = paste0(setting_description," ",setting_required, " (text_key: ", key, ")"), tags$label(HTML(setting_label))),
- span(class="num_charts",
- title = HTML(paste0(charts_rel, collapse="\n")),
- tags$label(paste0("(", length(charts_rel), ")"))),
- div(
- class="select-wrap",
- input,
- div(id = ns(tt_msg), title = "", tags$label(id = ns(msg), ""), class="status")
- )
- )
-}
diff --git a/inst/safetyGraphics_app/modules/renderSettings/util/createSettingLabel.R b/inst/safetyGraphics_app/modules/renderSettings/util/createSettingLabel.R
deleted file mode 100644
index d9d170fb..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/util/createSettingLabel.R
+++ /dev/null
@@ -1,13 +0,0 @@
-#' Create label for chart setting selector
-#'
-#' @param key A character key representing the setting of interest.
-#'
-#' @return A character string containing full HTML text to be used for input label. Contains info icon to
-#' indicate that description is available upon mouseover, setting label, and asterisk if setting is required.
-#'
-createSettingLabel <- function(key, metadata){
- sm <- getSettingsMetadata(text_keys=key, metadata=metadata)
- setting_label <- sm$label
- required <- sm$setting_required
- paste0(setting_label)#, " " )
-}
diff --git a/inst/safetyGraphics_app/modules/renderSettings/util/createSettingsSection.R b/inst/safetyGraphics_app/modules/renderSettings/util/createSettingsSection.R
deleted file mode 100644
index eca0959e..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/util/createSettingsSection.R
+++ /dev/null
@@ -1,24 +0,0 @@
-createSettingsSection <- function(class, label,cols,ns){
- section <-
- column(cols,
- wellPanel(
- class=paste0(class," section"),
- h3(
- label,
- materialSwitch(
- ns(paste0("show_",class)),
- label = "",
- right=TRUE,
- value = TRUE,
- status = "primary"
- )
- ),
- conditionalPanel(
- condition=paste0("input.show_",class),
- ns=ns,
- uiOutput(ns(paste0(class,"_ui")))
- )
- )
- )
- return(section)
-}
\ No newline at end of file
diff --git a/inst/safetyGraphics_app/modules/renderSettings/util/createSettingsUI.R b/inst/safetyGraphics_app/modules/renderSettings/util/createSettingsUI.R
deleted file mode 100644
index 16dc4eb3..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/util/createSettingsUI.R
+++ /dev/null
@@ -1,23 +0,0 @@
-#' Create UI for specified section of settings tab
-#'
-#' @param data A data frame to be used to populate control options
-#' @param settings A settings list to be used to populate control options
-#' @param setting_cat_val Settings category. One of "data","measure","appearance"
-#' @param charts A character vector containing names of charts of interest
-#' @param ns The namespace of the current module
-#'
-#' @return A list containing the UI code for all selectors in the specified settings category.
-createSettingsUI <- function(data, settings, setting_cat_val, charts, metadata, ns){
-
- #filter the metadata based on the charts option (if any)
- sm <- getSettingsMetadata(charts=charts, metadata=metadata) %>%
- filter(setting_cat==setting_cat_val)
-
- lapply(sm$text_key, function(key){
- createControl(key, metadata = sm, data, settings, ns)
- })
-}
-
-
-
-
diff --git a/inst/safetyGraphics_app/modules/renderSettings/util/updateSettingStatus.R b/inst/safetyGraphics_app/modules/renderSettings/util/updateSettingStatus.R
deleted file mode 100644
index a3003b57..00000000
--- a/inst/safetyGraphics_app/modules/renderSettings/util/updateSettingStatus.R
+++ /dev/null
@@ -1,29 +0,0 @@
-#' Update setting validation status
-#'
-#' Workflow:
-#' (1) Update abbreviated status for a given setting using green (valid) or red (invalid) text
-#' (2) Update long status message for a given setting to be displayed upon mouseover
-#'
-#' @param ns The namespace of the current module
-#' @param key A character key representing the setting of interest
-#' @param status_short Abbreviated validation message
-#' @param status_long Detailed validation message
-
-updateSettingStatus<-function(ns, key, status_short, status_long, icon){
-
- ctl_id<-paste0("ctl_", key)
- #TODO: get msg_ and tooltip_ selectors using relative position to control id
- msg_id <- paste0("msg_", key)
- tooltip_id <- paste0("tt_msg_", key)
- if(status_short=="OK"){
- shinyjs::addClass(id=ctl_id, class="valid")
- shinyjs::removeClass(id=ctl_id, class="invalid")
- }else{
- shinyjs::removeClass(id=ctl_id, class="valid")
- shinyjs::addClass(id=ctl_id, class="invalid")
- }
- shinyjs::html(id = msg_id, html = paste(icon))
- if(nchar(status_long)>0){
- shinyjs::runjs(paste0('$("#',ns(tooltip_id), '").attr("title", "',status_long,'").addClass("details")'))
- }
-}
diff --git a/inst/safetyGraphics_app/server.R b/inst/safetyGraphics_app/server.R
deleted file mode 100644
index fa2d103e..00000000
--- a/inst/safetyGraphics_app/server.R
+++ /dev/null
@@ -1,171 +0,0 @@
-# Server code for safetyGraphics App
-# - 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,
-# indicating whether user has satisfied requirements of that tab
-
-function(input, output, session){
-
-
- configVals <- reactiveValues()
-
- for (d in unique(settingsMetadata$domain)){
- configVals[[d]] <- callModule(config, d, domain = d, metadata=metadata_list, preload_data_list = preload_data_list)
- }
-
- charts <- reactive({
- out <- vector()
- for (i in names(configVals)){
- charts <- configVals[[i]]$charts()
- out <- c(out, charts)
- }
- return(out)
- })
-
- #toggle css class of chart tabs
- observeEvent(charts(),{
- for (chart in names(charts())){
- valid <- charts()[[chart]]
-
- ## code to toggle css for chart-specific tab here
- 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)
- }
- })
-
-
- # hide charts tab if no chart selected
- observeEvent(charts(),{
- if (is.null(charts())){
- hideTab(inputId = "nav_id", target = "Charts")
- hideTab(inputId = "nav_id", target = "Reports")
- }
- },
- ignoreNULL = FALSE,
- ignoreInit = TRUE) # so there's no hiding when the app first loads
-
-
- ##############################################################
- # Initialize Charts Modules
- ##############################################################
-
- # set up all chart tabs from the start
- for (chartnum in 1:nrow(chartsMetadata)){
- md <- chartsMetadata[chartnum,]
- chart<-md$chart
- chartLabel<-md$label
-
- appendTab(
- session = session,
- inputId ="nav_id",
- tab = tabPanel(
- title = chartLabel,
- value = chart,
- renderChartUI(paste0("chart", chart))
- ),
- menuName = "Charts"
- )
- }
-
- # hide/show chart tabs in response to user selections
- observe({
-
- # show charts and reports tabs if any charts are selected
- showTab(inputId = "nav_id", target = "Charts")
- showTab(inputId = "nav_id", target = "Reports")
-
- selected_charts <- names(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)
- }
- })
-
- for(chart in all_charts){
- chartType <- chartsMetadata %>% filter(chart==!!chart) %>% pull(type)
- width <- chartsMetadata %>% filter(chart==!!chart) %>% pull(maxWidth)
- domain <- chartsMetadata %>% filter(chart==!!chart) %>% pull(domain)
- callModule(
- module = renderChart,
- id = paste0("chart", chart),
- data = reactive(configVals[[domain]]$data()),
- settings = reactive(configVals[[domain]]$settings()),
- valid = reactive(charts()[[chart]]),
- chart = chart,
- type = chartType,
- width = width
- # type = "htmlwidget"
- )
-
- }
-
-
-
-
- callModule(
- module = renderReports,
- id = "reportsUI",
- config = configVals,
- chartsMetadata = reactive(chartsMetadata[chartsMetadata$chart %in% names(charts()),c("chart","label","domain")])
- )
-
-
- output$about <- renderUI({
- HTML(' Welcome to the Safety Graphics Shiny App
- The Safety Graphics Shiny app is an interactive tool for evaluating clinical trial safety using
- a flexible data pipeline. This application and corresponding
- safetyGraphics
- R package have been developed as part of the Interactive Safety Graphics (ISG) workstream
- of the ASA Biopharm-DIA Safety Working Group.
- Using the app
- Detailed instructions about using the app can be found in our
- vignette. In short,
- the user will begin by loading a data file, adjust settings as needed and view the interactive charts.
- Finally, the user may export a self-contained, fully reproducible snapshot of the charts that can be easily shared with others.
- Clinical Workflow
- This shiny app has been developed in parallel with a well-documented clinical workflow
- for monitoring hepatotoxicity. The workflow, written by expert physicians, provides a detailed description of how the interactive graphics can be used as part of a safety clinician’s monitoring practice.
- Interactive Charts
- The included interactive charts are built using the htmlwidgets framework in R. The code libraries
- and configuration details for the underlying JavaScript charts are located below.
-
-
-
- For more information about safetyGraphics, please visit our
-GitHub repository. We also welcome your suggestions in our
-issue tracker.
-
')
-
- })
-
- output$hex <- renderImage({
- list(src = system.file("safetyGraphicsHex/safetyGraphicsHex.png", package = "safetyGraphics"), width="60%")
- }, deleteFile = FALSE)
-
- session$onSessionEnded(stopApp)
-}
diff --git a/inst/safetyGraphics_app/tests/defaultPath-expected/001.json b/inst/safetyGraphics_app/tests/defaultPath-expected/001.json
deleted file mode 100644
index 28c83ec7..00000000
--- a/inst/safetyGraphics_app/tests/defaultPath-expected/001.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "output": {
- "chart_tab_title": {
- "html": "Chart <\/i>",
- "deps": [
-
- ]
- },
- "data_tab_title": {
- "html": "Data <\/i>",
- "deps": [
-
- ]
- },
- "settings_tab_title": {
- "html": "Settings <\/i>",
- "deps": [
-
- ]
- }
- }
-}
diff --git a/inst/safetyGraphics_app/tests/defaultPath-expected/001.png b/inst/safetyGraphics_app/tests/defaultPath-expected/001.png
deleted file mode 100644
index f6d8ef626061e407cb0f074fffd8a72f4afc6476..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 80017
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