diff --git a/DESCRIPTION b/DESCRIPTION index 5f73fad9..a278bb58 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -16,11 +16,12 @@ Depends: R (>= 4.0) License: MIT + file LICENSE Encoding: UTF-8 LazyData: true -RoxygenNote: 7.2.1 +RoxygenNote: 7.2.3 Suggests: ggplot2 (>= 3.3.0), knitr (>= 1.34), rmarkdown (>= 2.10), + safetyProfile, shinydashboard (>= 0.7.1), shinytest (>= 1.5.0), testthat (>= 3.0.4), diff --git a/NAMESPACE b/NAMESPACE index 61095cdd..725ee710 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ export("%>%") export(app_startup) export(chartsNav) +export(chartsNavUI) export(chartsTab) export(chartsTabUI) export(detectStandard) @@ -32,6 +33,8 @@ export(mappingSelectUI) export(mappingTab) export(mappingTabUI) export(prepareChart) +export(profileTab) +export(profileTabUI) export(safetyGraphicsApp) export(safetyGraphicsInit) export(safetyGraphicsServer) @@ -59,6 +62,8 @@ importFrom(magrittr,"%>%") importFrom(purrr,keep) importFrom(purrr,map) importFrom(purrr,map2) +importFrom(purrr,map_lgl) +importFrom(purrr,set_names) importFrom(rlang,.data) importFrom(shiny,dataTableOutput) importFrom(shiny,renderDataTable) @@ -72,6 +77,7 @@ importFrom(shinyjs,hide) importFrom(shinyjs,html) importFrom(shinyjs,removeClass) importFrom(shinyjs,show) +importFrom(shinyjs,toggleClass) importFrom(shinyjs,useShinyjs) importFrom(sortable,add_rank_list) importFrom(sortable,bucket_list) diff --git a/R/app_startup.R b/R/app_startup.R index 0f23cc26..1ae9250a 100644 --- a/R/app_startup.R +++ b/R/app_startup.R @@ -9,7 +9,9 @@ #' @param autoMapping boolean indicating whether the app should attempt to automatically detect data standards and generate mappings for the data provided. Values specified in the `mapping` parameter overwrite automatically generated mappings when both are found. Defaults to true. #' @param filterDomain domain used for the data/filter tab. Demographics ("`dm`") is used by default. Using a domain that is not one record per participant is not recommended. #' @param chartSettingsPaths path(s) where customization functions are saved relative to your working directory. All charts can have initialization (e.g. myChart_Init.R) and static charts can have charting functions (e.g. myGraphic_Chart.R). All R files in this folder are sourced and files with the correct naming convention are linked to the chart. See the Custom Charts vignette for more details. -#' +#' @param appName character string defining the name of the app (default = "safetyGraphics") +#' @param hexPath path to image file with a hex or other logo. safetyGraphics hex used by default. +#' @param homeTabPath path to html content to be used on the home page. default is a summary of the safetyGraphics framework. #' #' @return List of elements for used to initialize the shiny app with the following parameters #' \itemize{ @@ -21,7 +23,13 @@ #' } #' #' @export -app_startup<-function(domainData=NULL, meta=NULL, charts=NULL, mapping=NULL, autoMapping=NULL, filterDomain=NULL, chartSettingsPaths=NULL){ +app_startup<-function(domainData=NULL, meta=NULL, charts=NULL, mapping=NULL, autoMapping=NULL, filterDomain=NULL, chartSettingsPaths=NULL, appName=NULL, hexPath=NULL, homeTabPath=NULL){ + + # Set defaults for app name, hex and home page content. + if (!is.character(appName)) appName <- 'safetyGraphics' + if (!is.character(hexPath) || !file.exists(hexPath)) hexPath <- system.file("resources/safetyGraphicsHex.png", package = "safetyGraphics") + if (!is.character(homeTabPath) || !file.exists(homeTabPath)) homeTabPath <- system.file('resources/safetyGraphicsHomeTab.html', package = 'safetyGraphics') + # If charts are not provided, load them from chartSettingsPath or the safetyCharts package if(is.null(charts)){ if(is.null(chartSettingsPaths)){ @@ -47,18 +55,18 @@ app_startup<-function(domainData=NULL, meta=NULL, charts=NULL, mapping=NULL, aut message("- Dropped ", length(envDrops), " chart(s) with `env` paramter missing or not set to 'safetyGraphics': ",paste(names(envDrops), collapse=", ")) } charts <- charts %>% purrr::keep(~.x$envValid) - + #Drop charts if data for required domain(s) is not found domainDrops <- charts %>% purrr::keep(~(!all(.x$domain %in% names(domainData)))) if(length(domainDrops)>0){ message("- Dropped ", length(domainDrops), " chart(s) with missing data domains: ", paste(names(domainDrops), collapse=", ")) } charts <- charts %>% purrr::keep(~all(.x$domain %in% names(domainData))) - + # sort charts based on order chartOrder <- order(charts %>% map_dbl(~.x$order) %>% unlist()) charts <- charts[chartOrder] - + message("- Initializing app with ",length(charts), " chart(s).") # Set filterDomain to NULL if specified domain doesn't exist @@ -81,8 +89,11 @@ app_startup<-function(domainData=NULL, meta=NULL, charts=NULL, mapping=NULL, aut domainData=domainData, mapping=mappingObj$mapping, standards=mappingObj$standard, - filterDomain=filterDomain + filterDomain=filterDomain, + appName=appName, + hexPath=hexPath, + homeTabPath = homeTabPath ) return(config) -} \ No newline at end of file +} diff --git a/R/getChartStatus.R b/R/getChartStatus.R new file mode 100644 index 00000000..6037729e --- /dev/null +++ b/R/getChartStatus.R @@ -0,0 +1,120 @@ +#' Check the status of a chart based on the current mapping +#' +#' Checks a chart's status when column-level chart specifications are provided in chart$dataSpec. +#' Note that safetyGraphicsApp() does not allow a `mapping` value that is not found in `domainData`, +#' so this function only needs to check that an expected parameter exists in `mapping` (not that the +#' specified column is found in the loaded data). +#' +#' @param chart `list` chart object +#' @param mapping `data.frame` current mapping +#' +#' @return `list` Named list with properties: +#' - status `logical` +#' - domains `list` list specifying whether all columns are specified in each domain +#' - columns `list` list that matches the structure of chart$dataSpec and indicates which variables are available. +#' +#' @examples +#' sample_chart <- list( +#' domains=c("aes","dm"), +#' dataSpec=list( +#' aes=c("id_col","custom_col"), +#' dm=c("id_col","test_col") +#' ) +#' ) +#' +#' sample_mapping <- data.frame( +#' domain=c("aes","aes","dm","dm"), +#' text_key=c("id_col","custom_col","id_col","test_col"), +#' current=c("myID","AEcol","myID","dmCol") +#' ) +#' +#' check <- safetyGraphics:::getChartStatus(chart=sample_chart, mapping=sample_mapping) +#' # check$status=TRUE +#' +#' # Add data spec to each chart. +#' charts <- makeChartConfig() %>% +#' map(function(chart) { +#' chart$mapping <- chart$domain %>% +#' map_dfr(function(domain) { +#' do.call( +#' `::`, +#' list( +#' 'safetyCharts', +#' paste0('meta_', domain) +#' ) +#' ) +#' }) %>% +#' distinct(domain, col_key, current = standard_adam) %>% +#' filter(!is.na(current)) %>% +#' select(domain, text_key = col_key, current) +#' +#' chart$dataSpec <- chart$domain %>% +#' map(function(domain) { +#' chart$mapping %>% +#' filter(.data$domain == !!domain) %>% +#' pull(text_key) %>% +#' unique() +#' }) %>% +#' set_names(chart$domain) +#' +#' chart +#' }) +#' +#' checks <- map(charts, ~getChartStatus(.x, .x$mapping)) +#' +#' @importFrom purrr imap map +#' @importFrom rlang set_names +#' +#' @keywords internal +getChartStatus <- function(chart, mapping){ + stopifnot( + "Can't get status since chart does not have dataSpec associated."=hasName(chart, 'dataSpec') + ) + + # check to see whether each individual column has a mapping defined + missingCols<-c() + colStatus <- names(chart$dataSpec) %>% map(function(domain){ + domainMapping <- generateMappingList(settingsDF=mapping, domain=domain) + requiredCols <- chart$dataSpec[[domain]] + colStatus <- requiredCols %>% map(function(col){ + if(hasName(domainMapping,col)){ + status<-case_when( + domainMapping[[col]]=='' ~ FALSE, + is.na(domainMapping[[col]]) ~ FALSE, + is.character(domainMapping[[col]]) ~ TRUE + ) + } else{ + status<-FALSE + } + return(status) + }) %>% set_names(requiredCols) + return(colStatus) + })%>% set_names(names(chart$dataSpec)) + + # check to see whether all columns in a domain were valid + domainStatus <- colStatus %>% + map(~all(unlist(.x))) %>% + set_names(names(colStatus)) + + # check to see whether all columns in all domains were valid + status <- ifelse(all(unlist(domainStatus)),TRUE, FALSE) + + # make a text summary + if(status){ + summary <- "All required mappings found" + }else{ + missingCols <- colStatus %>% imap(function(cols,domain){ + missingDomainCols <- cols %>% imap(function(status, col){ + if(status){ + return(NULL) + }else{ + return(paste0(domain,"$",col)) + } + }) + return(missingDomainCols) + }) + summary<- paste0("Missing Mappings: ",paste(unlist(missingCols),collapse=",")) + } + + return(list(chart=chart$name, columns=colStatus, domains=domainStatus, status=status, summary=summary)) +} diff --git a/R/makeChartSummary.R b/R/makeChartSummary.R index 450b30f9..97882fb8 100644 --- a/R/makeChartSummary.R +++ b/R/makeChartSummary.R @@ -2,12 +2,24 @@ #' @description makes a nicely formatted html summary for a chart object #' #' @param chart list containing chart specifications +#' @param status (optional) chart status from `getChartStatus`. Default is NULL. #' @param showLinks boolean indicating whether to include links #' @param class character to include as class #' #' @export -makeChartSummary<- function(chart, showLinks=TRUE, class="chart-header"){ +makeChartSummary<- function(chart, status=NULL, showLinks=TRUE, class="chart-header"){ + + if(!is.null(status)){ + if(status$status){ + status <- div(class="status", tags$small("Status"), tags$i(class="fa fa-check-circle", style="color: green"),title=status$summary) + }else{ + status <- div(class="status", tags$small("Status"), tags$i(class="fa fa-times-circle", style="color: red"),title=status$summary) + } + }else{ + status <- NULL + } + if(utils::hasName(chart,"links")){ links<-purrr::map2( chart$links, @@ -23,17 +35,20 @@ makeChartSummary<- function(chart, showLinks=TRUE, class="chart-header"){ }else{ links<-NULL } - - labelDiv<-div(tags$small("Chart"),chart$label) - typeDiv<-div(tags$small("Type"), chart$type) - dataDiv<-div(tags$small("Data Domain"), paste(chart$domain,collapse=" ")) + labelDiv<-div(class="name", tags$small("Chart"),chart$label) + typeDiv<-div(class="type", tags$small("Type"), chart$type) + dataDiv<-div(class="domain", tags$small("Data Domain"), paste(chart$domain,collapse=" ")) + + class <- c('chart-summary',class) + if(showLinks){ summary<-div( labelDiv, typeDiv, dataDiv, links, + status, class=class ) } else { @@ -41,6 +56,7 @@ makeChartSummary<- function(chart, showLinks=TRUE, class="chart-header"){ labelDiv, typeDiv, dataDiv, + status, class=class ) } diff --git a/R/mod_chartsNav.R b/R/mod_chartsNav.R index 6904a81e..d2c3fd76 100644 --- a/R/mod_chartsNav.R +++ b/R/mod_chartsNav.R @@ -1,22 +1,65 @@ #' Adds a navbar tab that initializes the Chart Module UI #' -#' @param chart chart metadata -#' @param ns namespace +#' @param id module id +#' @param chart chart metadata #' #' @export #' -chartsNav <- function(chart,ns){ - appendTab( - inputId = "safetyGraphicsApp", - menuName = "Charts", - tab = tabPanel( - title = makeChartSummary(chart, showLinks=FALSE, class="chart-nav"), - value = chart$name, - chartsTabUI( - id=ns(chart$name), - chart=chart - ) +chartsNavUI <- function(id, chart) { + ns <- NS(id) + + panel<-tabPanel( + title = uiOutput(ns("tabTitle")), + value = chart$name, + chartsTabUI( + id=ns("chart"), + chart=chart ) ) -} \ No newline at end of file + + return(panel) +} + +#' Server for a navbar tab +#' +#' @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 chart list containing a safetyGraphics chart object like those returned by \link{makeChartConfig}. +#' @param data named list of current data sets (Reactive). +#' @param mapping tibble capturing the current data mappings (Reactive). +#' +#' @export +#' + +chartsNav<-function(input, output, session, chart, data, mapping){ + ns <- session$ns + + chartStatus <- reactive({ + if(hasName(chart, 'dataSpec')){ + status<-getChartStatus(chart, mapping()) + }else{ + status<-NULL + } + return(status) + }) + + output$tabTitle <- renderUI({ + makeChartSummary( + chart, + status=chartStatus(), + showLinks=FALSE, + class="chart-nav" + ) + }) + + callModule( + module=chartsTab, + id='chart', + chart=chart, + data=data, + mapping=mapping, + status=chartStatus + ) +} diff --git a/R/mod_chartsTab.R b/R/mod_chartsTab.R index 7b274b5f..b27c1b63 100644 --- a/R/mod_chartsTab.R +++ b/R/mod_chartsTab.R @@ -10,7 +10,7 @@ chartsTabUI <- function(id, chart){ ns <- shiny::NS(id) - header<-div(class=ns("header"), makeChartSummary(chart)) + header<- uiOutput(ns("chart-header")) chartWrap<-chart$functions$ui(ns("chart-wrap")) return(list(header, chartWrap)) @@ -24,12 +24,16 @@ chartsTabUI <- function(id, chart){ #' @param chart list containing a safetyGraphics chart object like those returned by \link{makeChartConfig}. #' @param data named list of current data sets (Reactive). #' @param mapping tibble capturing the current data mappings (Reactive). +#' @param status chart status (Reactive) #' #' @export -chartsTab <- function(input, output, session, chart, data, mapping){ +chartsTab <- function(input, output, session, chart, data, mapping, status){ ns <- session$ns - + + # Draw the header + output$`chart-header` <- renderUI({makeChartSummary(chart, status=status())}) + # Initialize chart-specific parameters params <- reactive({ makeChartParams( @@ -56,7 +60,7 @@ chartsTab <- function(input, output, session, chart, data, mapping){ where="beforeEnd", ui=downloadButton(ns("scriptDL"), "R script", class="pull-right btn-xs dl-btn") ) - + mapping_list<-reactive({ mapping_list <- generateMappingList(mapping() %>% filter(.data$domain %in% chart$domain)) if(length(mapping_list)==1){ @@ -93,7 +97,7 @@ chartsTab <- function(input, output, session, chart, data, mapping){ mapping = mapping(), chart = chart ) - + rmarkdown::render( tempReport, output_file = file, diff --git a/R/mod_homeTab.R b/R/mod_homeTab.R index 06f83ede..d942bd4e 100644 --- a/R/mod_homeTab.R +++ b/R/mod_homeTab.R @@ -4,11 +4,11 @@ #' #' @export -homeTabUI <- function(id){ +homeTabUI <- function(id) { ns <- NS(id) fluidRow( - column(width=8, style='font-size:20px', uiOutput(outputId = ns("about"))), - column(width=4, imageOutput(outputId = ns("hex"))) + column(width=9, style='font-size:20px', uiOutput(outputId = ns("about"))), + column(width=3, imageOutput(outputId = ns("hex"))) ) } @@ -20,40 +20,20 @@ homeTabUI <- function(id){ #' #' @export -homeTab <- function(input, output, session){ +homeTab <- function(input, output, session, config) { ns <- session$ns - 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 initialize the app with their data, 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. -

-

Charts

-

- The app is built to support a wide variety of chart types including static plots (e.g. from {ggplot2}), shiny modules, {htmlwidgets} and even static outputs like RTFs. - Several pre-configured charts are included in the companion {safetyCharts} R Package, and are available by default in the app. - Other charts can be added using the process descibed in this vignette. -

- -

- For more information about {safetyGraphics}, please visit our GitHub repository. - We also welcome your suggestions in our issue tracker. -

- ') + output$about <- renderUI({ + HTML(readLines(config$homeTabPath)) }) - - output$hex <- renderImage({ - list( - src = system.file("safetyGraphicsHex/safetyGraphicsHex.png", package = "safetyGraphics"), width="60%") - }, deleteFile = FALSE + + output$hex <- renderImage( + { + list( + src = config$hexPath, + width = "100%" + ) + }, + deleteFile = FALSE ) } diff --git a/R/mod_mappingColumn.R b/R/mod_mappingColumn.R index a08d2f29..b9b7ea25 100644 --- a/R/mod_mappingColumn.R +++ b/R/mod_mappingColumn.R @@ -33,7 +33,7 @@ mappingColumnUI <- function(id, meta, data, mapping=NULL){ col_meta <- meta %>% filter(.data$type=="column") # Exactly one column mapping provided - stopifnot(nrow(col_meta)==1) + stopifnot(msg = nrow(col_meta)==1) col_ui[[1]] <- mappingSelectUI( ns(col_meta$text_key), diff --git a/R/mod_profileTab.R b/R/mod_profileTab.R new file mode 100644 index 00000000..9c305358 --- /dev/null +++ b/R/mod_profileTab.R @@ -0,0 +1,56 @@ +#' @title UI for the profile module in safetyProfile::profile_ui +#' +#' @param id module id +#' +#' @export + +profileTabUI <- function(id){ + ns <- NS(id) + + if(isNamespaceLoaded("safetyProfile")){ + profile_ui<-profile_ui(ns("profile")) + }else{ + profile_ui<-NULL + } + return(profile_ui) +} + + +#' @title Server for the patient profile in safetyProfile::profile_server +#' +#' @param input Shiny input object +#' @param output Shiny output object +#' @param session Shiny session object +#' @param params reactive containing mapping and data +#' @param current_id reactive containing currently selected participant +#' +#' @return current_id +#' +#' @import datamods +#' @importFrom shinyjs show hide +#' @importFrom shiny renderDataTable +#' +#' @export + +profileTab <- function(input, output, session, params) { + id <- safetyProfile::profile_server( + "profile", + params + ) + + observe({ + shinyjs::html( + "pt-header", + id(), + asis=TRUE + ) + + shinyjs::toggleClass( + selector = "#pt-header", + class = "active", + condition = !is.null(id()) + ) + }) + + return(id) +} diff --git a/R/mod_safetyGraphicsServer.R b/R/mod_safetyGraphicsServer.R index 0d7e4b85..dae54fa5 100644 --- a/R/mod_safetyGraphicsServer.R +++ b/R/mod_safetyGraphicsServer.R @@ -14,15 +14,34 @@ #' @import shiny #' @import dplyr #' @importFrom purrr map -#' @importFrom shinyjs html +#' @importFrom shinyjs html toggleClass #' #' @export -safetyGraphicsServer <- function(input, output, session, meta, mapping, domainData, charts, filterDomain){ - #Initialize modules - current_mapping<-callModule(mappingTab, "mapping", meta, domainData) - - # Initialize the filter tab +safetyGraphicsServer <- function(input, output, session, + meta, + mapping, + domainData, + charts, + filterDomain, + config +) { + #--- Home tab ---# + callModule( + homeTab, + "home", + config + ) + + #--- Mapping tab ---# + current_mapping<-callModule( + mappingTab, + "mapping", + meta, + domainData + ) + + #--- Filter tab ---# filtered_data<-callModule( filterTab, "filter", @@ -30,25 +49,54 @@ safetyGraphicsServer <- function(input, output, session, meta, mapping, domainDa filterDomain=filterDomain, current_mapping=current_mapping ) - - callModule(homeTab, "home") - #Initialize Chart UI - Adds subtabs to chart menu - this initializes initializes chart UIs - charts %>% purrr::map(~chartsNav(.x,session$ns)) + #--- Profile tab ---# + if(isNamespaceLoaded("safetyProfile")){ + callModule( + profileTab, + "profile", + params = reactive({ + list( + data=filtered_data(), + settings=safetyGraphics::generateMappingList(current_mapping()) + ) + }) + ) + + observeEvent(input$participants_selected, { + cli::cli_alert_info('Selected participant ID: {input$participants_selected}') - #Initialize Chart Servers - validDomains <- tolower(names(mapping)) - charts %>% purrr::map( + # Update selected participant. + updateSelectizeInput( + session, + inputId = 'profile-profile-id-idSelect', + selected = input$participants_selected + ) + }) + } else { + shinyjs::hide(selector = paste0(".navbar li a[data-value='profile']")) + shinyjs::hide(selector = paste0(".navbar #pt-header")) + } + + #--- Charts tab ---# + charts %>% purrr::walk( ~callModule( - module=chartsTab, + module=chartsNav, id=.x$name, chart=.x, data=filtered_data, - mapping=current_mapping + mapping=current_mapping ) ) - #Setting tab - callModule(settingsTab, "settings", domains = domainData, metadata=meta, mapping=current_mapping, charts = charts) + #--- Settings tab ---# + callModule( + settingsTab, + "settings", + domains = domainData, + metadata=meta, + mapping=current_mapping, + charts = charts + ) } diff --git a/R/mod_safetyGraphicsUI.R b/R/mod_safetyGraphicsUI.R index 523b0a22..c6ff6051 100644 --- a/R/mod_safetyGraphicsUI.R +++ b/R/mod_safetyGraphicsUI.R @@ -1,7 +1,7 @@ #' UI for the core safetyGraphics app including Home, Mapping, Filter, Charts and Settings modules. #' -#' #' @param id module ID +#' @param charts list of charts in the format produced by safetyGraphics::makeChartConfig() #' @param meta data frame containing the metadata for use in the app. #' @param domainData named list of data.frames to be loaded in to the app. #' @param mapping data.frame specifying the initial values for each data mapping. If no mapping is provided, the app will attempt to generate one via \code{detectStandard()} @@ -11,7 +11,14 @@ #' #' @export -safetyGraphicsUI <- function(id, meta, domainData, mapping, standards){ +safetyGraphicsUI <- function(id, + meta, + mapping, + domainData, + charts, + standards, + config +) { ns<-NS(id) #read css from package @@ -31,12 +38,48 @@ safetyGraphicsUI <- function(id, meta, domainData, mapping, standards){ "\");" )) ) + + pt_selected<-tags$script( + HTML(paste0( + "var header = $('.navbar > .container-fluid');", + "header.append(\"", + "
", + "None", + "
", + "\");", + "var ptheader = $('.navbar > .container-fluid > #pt-header');", + "ptheader.on('click',function(){", + "$('", + '[data-value="profile"]', + "').tab('show');", + "})" + )) + ) + if(isNamespaceLoaded("shinybusy")){ spinner<-shinybusy::add_busy_spinner(spin = "atom", position="bottom-right") }else{ spinner<-NULL } + #Set up ChartNav + #trick for navbar menu: https://stackoverflow.com/questions/34846469/for-loops-lapply-navbarpage-within-in-ui-r + chartList <- charts %>% + purrr::map(function(chart) { + chartsNavUI(ns(chart$name), chart) + }) %>% + unname + + navParams<-c( + list( + title='Charts', + icon=icon("chart-bar") + ), + chartList + ) + + chartNav <- do.call(navbarMenu, navParams) + #app UI using calls to modules ui<-tagList( shinyjs::useShinyjs(), @@ -50,15 +93,22 @@ safetyGraphicsUI <- function(id, meta, domainData, mapping, standards){ ) ), navbarPage( - "safetyGraphics", + config$appName, id=ns("safetyGraphicsApp"), - tabPanel("Home", icon=icon("home"),homeTabUI(ns("home"))), + tabPanel("Home", icon=icon("home"), homeTabUI(ns("home"))), tabPanel("Mapping", icon=icon("map"), mappingTabUI(ns("mapping"), meta, domainData, mapping, standards)), tabPanel("Filtering", icon=icon("filter"), filterTabUI(ns("filter"))), - navbarMenu('Charts', icon=icon("chart-bar")), + tabPanel( + "Profile", + icon=icon("person"), + value='profile', + profileTabUI(ns("profile")) + ), + chartNav, tabPanel('',icon=icon("cog"), settingsTabUI(ns("settings"))) ), - participant_badge + participant_badge, + pt_selected ) return(ui) } diff --git a/R/prepareChart.R b/R/prepareChart.R index 71ffebc4..cc19aecf 100644 --- a/R/prepareChart.R +++ b/R/prepareChart.R @@ -37,6 +37,14 @@ prepareChart <- function(chart){ tolower(chart$env)=="safetygraphics" ) + # check to see if data specifications are provided in domain + if(typeof(chart$domain)=="list"){ + if(!hasName(chart,"dataSpec")){ + chart$dataSpec <- chart$domain + } + chart$domain <- names(chart$domain) + } + #### Bind Workflow functions to chart object #### if(!hasName(chart,"functions")){ diff --git a/R/safetyGraphicsApp.R b/R/safetyGraphicsApp.R index 9ba1a102..15525ff4 100644 --- a/R/safetyGraphicsApp.R +++ b/R/safetyGraphicsApp.R @@ -7,6 +7,10 @@ #' @param autoMapping boolean indicating whether the app should attempt to automatically detect data standards and generate mappings for the data provided. Values specified in the `mapping` parameter overwrite automatically generated mappings when both are found. Defaults to true. #' @param filterDomain domain used for the data/filter tab. Demographics ("`dm`") is used by default. Using a domain that is not one record per participant is not recommended. #' @param chartSettingsPaths path(s) where customization functions are saved relative to your working directory. All charts can have initialization (e.g. myChart_Init.R) and static charts can have charting functions (e.g. myGraphic_Chart.R). All R files in this folder are sourced and files with the correct naming convention are linked to the chart. See the Custom Charts vignette for more details. +#' @param appName character string defining the name of the app (default = "safetyGraphics") +#' @param hexPath path to image file with a hex or other logo. safetyGraphics hex used by default. +#' @param homeTabPath path to html content to be used on the home page. default is a summary of the safetyGraphics framework. +#' @param launchBrowser boolean indicating whether to launch the app in a browser. default is false #' @param runNow Should the shiny app object created be run directly? Helpful when writing functions to dispatch to shinyapps, rsconnect, or shinyproxy. #' #' @import shiny @@ -15,39 +19,66 @@ #' @export safetyGraphicsApp <- function( - domainData=list( - labs=safetyData::adam_adlbc, - aes=safetyData::adam_adae, - dm=safetyData::adam_adsl + domainData = list( + labs = safetyData::adam_adlbc, + aes = safetyData::adam_adae, + dm = safetyData::adam_adsl ), meta = NULL, - charts=NULL, - mapping=NULL, - autoMapping=TRUE, - filterDomain="dm", + charts = NULL, + mapping = NULL, + autoMapping = TRUE, + filterDomain = "dm", chartSettingsPaths = NULL, + appName = 'safetyGraphics', + hexPath = system.file("resources/safetyGraphicsHex.png", package = "safetyGraphics"), + homeTabPath = system.file('resources/safetyGraphicsHomeTab.html', package = 'safetyGraphics'), + launchBrowser = FALSE, runNow = TRUE ){ message("Initializing safetyGraphicsApp") - config <- app_startup(domainData, meta, charts, mapping, autoMapping, filterDomain, chartSettingsPaths) + + config <- app_startup( + meta = meta, + mapping = mapping, + domainData = domainData, + charts = charts, + filterDomain = filterDomain, + autoMapping = autoMapping, + chartSettingsPaths = chartSettingsPaths, + appName = appName, + hexPath = hexPath, + homeTabPath = homeTabPath + ) app <- shinyApp( - ui = safetyGraphicsUI("sg",config$meta, config$domainData, config$mapping, config$standards), - server = function(input,output,session){ - callModule( - safetyGraphicsServer, - "sg", - config$meta, - config$mapping, - config$domainData, - config$charts, - config$filterDomain + ui = safetyGraphicsUI(id = "sg", + meta = config$meta, + mapping = config$mapping, + domainData = config$domainData, + charts = config$charts, + standards = config$standards, + config = config + ), + server = function(input, output, session) { + callModule(safetyGraphicsServer, id = 'sg', + meta = config$meta, + mapping = config$mapping, + domainData = config$domainData, + charts = config$charts, + filterDomain = config$filterDomain, + config = config ) } ) - - if(runNow) - runApp(app) - else - app + + if (runNow) { + if (launchBrowser == TRUE) { + runApp(app, launch.browser = TRUE) + } else { + runApp(app) + } + } + + app } diff --git a/R/safetyGraphicsInit.R b/R/safetyGraphicsInit.R index 39762f63..bd5dc165 100644 --- a/R/safetyGraphicsInit.R +++ b/R/safetyGraphicsInit.R @@ -116,37 +116,39 @@ safetyGraphicsInit <- function(charts=makeChartConfig(), delayTime=1000, maxFile observeEvent(input$runApp,{ shinyjs::hide(id="init") shinyjs::show(id="sg-app") - config<- app_startup( - domainData = domainData() %>% keep(~!is.null(.x)), + + config <- app_startup( meta = NULL, - charts= charts(), #mapping=NULL, - filterDomain="dm", - autoMapping=TRUE, + domainData = domainData() %>% + keep(~!is.null(.x)), + charts = charts(), + filterDomain = "dm", + autoMapping = TRUE, #chartSettingsPaths = NULL ) output$sg <- renderUI({ - safetyGraphicsUI( - "sg", - config$meta, - config$domainData, - config$mapping, - config$standards + safetyGraphicsUI("sg", + meta = config$meta, + mapping = config$mapping, + domainData = config$domainData, + charts = config$charts, + standards = config$standards, + config = config ) }) # delay is needed to get the appendTab in mod_chartsNav to trigger properly shinyjs::delay( delayTime, - callModule( - safetyGraphicsServer, - "sg", - config$meta, - config$mapping, - config$domainData, - config$charts, - config$filterDomain + callModule(safetyGraphicsServer, "sg", + meta = config$meta, + mapping = config$mapping, + domainData = config$domainData, + charts = config$charts, + filterDomain = config$filterDomain, + config = config ) ) }) diff --git a/R/detectStandard.R b/R/util-detectStandard.R similarity index 100% rename from R/detectStandard.R rename to R/util-detectStandard.R diff --git a/R/evaluateStandard.R b/R/util-evaluateStandard.R similarity index 100% rename from R/evaluateStandard.R rename to R/util-evaluateStandard.R diff --git a/R/generateMappingList.R b/R/util-generateMappingList.R similarity index 82% rename from R/generateMappingList.R rename to R/util-generateMappingList.R index 97c13abe..53d53374 100644 --- a/R/generateMappingList.R +++ b/R/util-generateMappingList.R @@ -7,17 +7,25 @@ #' @importFrom stringr str_split #' @export -generateMappingList <- function(settingsDF, domain=NULL, pull=FALSE){ +generateMappingList <- function(settingsDF, domain=NULL, pull=FALSE) { + if ('tbl_df' %in% class(settingsDF)) + pull <- TRUE + settingsList <- list() settingsDF$domain_key <- paste0(settingsDF$domain, "--", settingsDF$text_key) domain_keys <- settingsDF$domain_key %>% textKeysToList() - + settingsList<-list() for (i in 1:length(domain_keys) ) { settingsList<-setMappingListValue( key=domain_keys[[i]], - value=ifelse(pull, settingsDF[i,"current"]%>%pull(), settingsDF[i,"current"]), + value=ifelse( + pull, + settingsDF[i,"current"] %>% + pull(), + settingsDF[i,"current"] + ), settings=settingsList, forceCreate=TRUE ) @@ -30,4 +38,4 @@ generateMappingList <- function(settingsDF, domain=NULL, pull=FALSE){ }else{ return(settingsList[[domain]]) } -} \ No newline at end of file +} diff --git a/R/hasColumn.R b/R/util-hasColumn.R similarity index 100% rename from R/hasColumn.R rename to R/util-hasColumn.R diff --git a/R/hasField.R b/R/util-hasField.R similarity index 100% rename from R/hasField.R rename to R/util-hasField.R diff --git a/R/setMappingListValue.R b/R/util-setMappingListValue.R similarity index 99% rename from R/setMappingListValue.R rename to R/util-setMappingListValue.R index 8debec94..344b4c69 100644 --- a/R/setMappingListValue.R +++ b/R/util-setMappingListValue.R @@ -17,7 +17,6 @@ setMappingListValue <- function(key, value, settings, forceCreate=FALSE){ - if(typeof(settings)!="list"){ if(forceCreate){ settings=list() @@ -38,4 +37,4 @@ setMappingListValue <- function(key, value, settings, forceCreate=FALSE){ settings[[firstKey]]<-setMappingListValue(settings = settings[[firstKey]],key = key[2:length(key)], value=value, forceCreate=forceCreate) return(settings) } -} \ No newline at end of file +} diff --git a/R/textKeysToList.R b/R/util-textKeysToList.R similarity index 100% rename from R/textKeysToList.R rename to R/util-textKeysToList.R diff --git a/README.md b/README.md index 0253c04d..fcf7db55 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![R build status](https://www.github.com/safetyGraphics/safetyGraphics/workflows/R-CMD-check-main/badge.svg)](https://github.com/SafetyGraphics/safetyGraphics/actions) -# safetyGraphics: Clinical Trial Monitoring with R +# safetyGraphics: Clinical Trial Monitoring with R The {safetyGraphics} package provides a framework for evaluating of clinical trial safety in R using a flexible data pipeline. The package includes a shiny application that allows users to explore safety data using a series of interactive graphics, including the hepatic safety explorer shown below. The package has been developed as part of the Interactive Safety Graphics (ISG) workstream of the ASA Biopharm-DIA Safety Working Group. diff --git a/docs/index.html b/docs/index.html index 08e33b9b..65dd2923 100644 --- a/docs/index.html +++ b/docs/index.html @@ -95,7 +95,7 @@

R build status

-