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(' -
- 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. -
- -- 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. -
-- 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(\"", + "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.
+ 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. +
+ ++ 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. +
+ ++ 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. +
diff --git a/inst/safetyGraphicsHex/safetyHex.R b/inst/resources/safetyHex.R similarity index 65% rename from inst/safetyGraphicsHex/safetyHex.R rename to inst/resources/safetyHex.R index 716015cc..62102c17 100644 --- a/inst/safetyGraphicsHex/safetyHex.R +++ b/inst/resources/safetyHex.R @@ -1,10 +1,10 @@ #library(hexSticker) #red on white -imgurl <- "./inst/safetyGraphicsHex/noun_heart_rate_210541_ec5d57.png" +imgurl <- "./inst/resources/noun_heart_rate_210541_ec5d57.png" # sticker( # imgurl, -# filename="./inst/safetyGraphicsHex/safetyGraphicsHex.png", +# filename="./inst/resources/safetyGraphicsHex.png", # package = "safetyGraphics", # p_color = "#666666", # p_size = 5, diff --git a/inst/www/index.css b/inst/www/index.css index ded3585c..56a5e7de 100644 --- a/inst/www/index.css +++ b/inst/www/index.css @@ -64,8 +64,20 @@ table.metatable.dataTable tr > td:last-of-type, table.metatable.trdataTable tr > margin-top:1em; } +#pt-header.active { + background:#4B9CD3; + text-decoration:underline; + cursor:pointer; +} + +#pt-header { + float: right; + margin-top: 1em; + margin-right:1em; +} + #population-header.subset { - background: blue; + background: green; } #dataSettings-previews .nav-tabs{ @@ -130,12 +142,12 @@ table.metatable.dataTable tr > td:last-of-type, table.metatable.trdataTable tr > display:inline-block; } -.chart-nav div:not(:first-child){ +.chart-nav div:not(.name){ font-size:0.8em; color: #999; } -.chart-nav div:last-child::before{ +.chart-nav div.domain::before{ content: "/"; } @@ -152,4 +164,9 @@ table.metatable.dataTable tr > td:last-of-type, table.metatable.trdataTable tr > border:1px solid #bee5eb; } +/* Right align chart status */ +div.chart-header div.status{ + float:right; + cursor:help +} diff --git a/man/app_startup.Rd b/man/app_startup.Rd index cccaa5ba..258ed325 100644 --- a/man/app_startup.Rd +++ b/man/app_startup.Rd @@ -11,7 +11,10 @@ app_startup( mapping = NULL, autoMapping = NULL, filterDomain = NULL, - chartSettingsPaths = NULL + chartSettingsPaths = NULL, + appName = NULL, + hexPath = NULL, + homeTabPath = NULL ) } \arguments{ @@ -28,6 +31,12 @@ app_startup( \item{filterDomain}{domain used for the data/filter tab. Demographics ("\code{dm}") is used by default. Using a domain that is not one record per participant is not recommended.} \item{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.} + +\item{appName}{character string defining the name of the app (default = "safetyGraphics")} + +\item{hexPath}{path to image file with a hex or other logo. safetyGraphics hex used by default.} + +\item{homeTabPath}{path to html content to be used on the home page. default is a summary of the safetyGraphics framework.} } \value{ List of elements for used to initialize the shiny app with the following parameters diff --git a/man/chartsNav.Rd b/man/chartsNav.Rd index d0197e34..b46e9f65 100644 --- a/man/chartsNav.Rd +++ b/man/chartsNav.Rd @@ -2,15 +2,23 @@ % Please edit documentation in R/mod_chartsNav.R \name{chartsNav} \alias{chartsNav} -\title{Adds a navbar tab that initializes the Chart Module UI} +\title{Server for a navbar tab} \usage{ -chartsNav(chart, ns) +chartsNav(input, output, session, chart, data, mapping) } \arguments{ -\item{chart}{chart metadata} +\item{input}{Input objects from module namespace} -\item{ns}{namespace} +\item{output}{Output objects from module namespace} + +\item{session}{An environment that can be used to access information and functionality relating to the session} + +\item{chart}{list containing a safetyGraphics chart object like those returned by \link{makeChartConfig}.} + +\item{data}{named list of current data sets (Reactive).} + +\item{mapping}{tibble capturing the current data mappings (Reactive).} } \description{ -Adds a navbar tab that initializes the Chart Module UI +Server for a navbar tab } diff --git a/man/chartsNavUI.Rd b/man/chartsNavUI.Rd new file mode 100644 index 00000000..bc3cea1f --- /dev/null +++ b/man/chartsNavUI.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mod_chartsNav.R +\name{chartsNavUI} +\alias{chartsNavUI} +\title{Adds a navbar tab that initializes the Chart Module UI} +\usage{ +chartsNavUI(id, chart) +} +\arguments{ +\item{id}{module id} + +\item{chart}{chart metadata} +} +\description{ +Adds a navbar tab that initializes the Chart Module UI +} diff --git a/man/chartsTab.Rd b/man/chartsTab.Rd index 090efb6c..46d4985b 100644 --- a/man/chartsTab.Rd +++ b/man/chartsTab.Rd @@ -4,7 +4,7 @@ \alias{chartsTab} \title{Server for chart module, designed to be re-used for each chart generated.} \usage{ -chartsTab(input, output, session, chart, data, mapping) +chartsTab(input, output, session, chart, data, mapping, status) } \arguments{ \item{input}{Input objects from module namespace} @@ -18,6 +18,8 @@ chartsTab(input, output, session, chart, data, mapping) \item{data}{named list of current data sets (Reactive).} \item{mapping}{tibble capturing the current data mappings (Reactive).} + +\item{status}{chart status (Reactive)} } \description{ Server for chart module, designed to be re-used for each chart generated. diff --git a/man/getChartStatus.Rd b/man/getChartStatus.Rd new file mode 100644 index 00000000..be1a5a86 --- /dev/null +++ b/man/getChartStatus.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/getChartStatus.R +\name{getChartStatus} +\alias{getChartStatus} +\title{Check the status of a chart based on the current mapping} +\usage{ +getChartStatus(chart, mapping) +} +\arguments{ +\item{chart}{chart object} + +\item{mapping}{the current mapping data.frame} +} +\value{ +a list with \code{status}, \code{domains} and \code{columns} properties +} +\description{ +Checks a chart's status when column-level chart specifications are provided in chart$dataSpec. Note that safetyGraphicsApp() does not allow a \code{mapping} value that is not found in \code{domainData}, so this function only needs to check that an expected parameter exists in \code{mapping} (not that the specified column is found in the loaded data). +} +\details{ +Returns a list, with: +\itemize{ +\item \code{status} (TRUE, FALSE) +\item \code{domains} a list specifying wheter all columns are specified in each domain +\item \code{columns} a 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 + +} +\keyword{internal} diff --git a/man/homeTab.Rd b/man/homeTab.Rd index 17447951..08da5ed2 100644 --- a/man/homeTab.Rd +++ b/man/homeTab.Rd @@ -4,7 +4,7 @@ \alias{homeTab} \title{Server for the filter module in datamods::filter_data_ui} \usage{ -homeTab(input, output, session) +homeTab(input, output, session, config) } \arguments{ \item{input}{mod input} diff --git a/man/makeChartSummary.Rd b/man/makeChartSummary.Rd index e5e0ef9e..1bdda058 100644 --- a/man/makeChartSummary.Rd +++ b/man/makeChartSummary.Rd @@ -4,11 +4,18 @@ \alias{makeChartSummary} \title{html chart summary} \usage{ -makeChartSummary(chart, showLinks = TRUE, class = "chart-header") +makeChartSummary( + chart, + status = NULL, + showLinks = TRUE, + class = "chart-header" +) } \arguments{ \item{chart}{list containing chart specifications} +\item{status}{(optional) chart status from \code{getChartStatus}. Default is NULL.} + \item{showLinks}{boolean indicating whether to include links} \item{class}{character to include as class} diff --git a/man/profileTab.Rd b/man/profileTab.Rd new file mode 100644 index 00000000..d74e9f09 --- /dev/null +++ b/man/profileTab.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mod_profileTab.R +\name{profileTab} +\alias{profileTab} +\title{Server for the patient profile in safetyProfile::profile_server} +\usage{ +profileTab(input, output, session, params) +} +\arguments{ +\item{input}{Shiny input object} + +\item{output}{Shiny output object} + +\item{session}{Shiny session object} + +\item{params}{reactive containing mapping and data} + +\item{current_id}{reactive containing currently selected participant} +} +\value{ +current_id +} +\description{ +Server for the patient profile in safetyProfile::profile_server +} diff --git a/man/profileTabUI.Rd b/man/profileTabUI.Rd new file mode 100644 index 00000000..0db64c61 --- /dev/null +++ b/man/profileTabUI.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mod_profileTab.R +\name{profileTabUI} +\alias{profileTabUI} +\title{UI for the profile module in safetyProfile::profile_ui} +\usage{ +profileTabUI(id) +} +\arguments{ +\item{id}{module id} +} +\description{ +UI for the profile module in safetyProfile::profile_ui +} diff --git a/man/safetyGraphicsApp.Rd b/man/safetyGraphicsApp.Rd index 5ff05020..4936c37a 100644 --- a/man/safetyGraphicsApp.Rd +++ b/man/safetyGraphicsApp.Rd @@ -13,6 +13,11 @@ safetyGraphicsApp( 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 ) } @@ -31,6 +36,14 @@ safetyGraphicsApp( \item{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.} +\item{appName}{character string defining the name of the app (default = "safetyGraphics")} + +\item{hexPath}{path to image file with a hex or other logo. safetyGraphics hex used by default.} + +\item{homeTabPath}{path to html content to be used on the home page. default is a summary of the safetyGraphics framework.} + +\item{launchBrowser}{boolean indicating whether to launch the app in a browser. default is false} + \item{runNow}{Should the shiny app object created be run directly? Helpful when writing functions to dispatch to shinyapps, rsconnect, or shinyproxy.} } \description{ diff --git a/man/safetyGraphicsServer.Rd b/man/safetyGraphicsServer.Rd index ece88603..8aadddf9 100644 --- a/man/safetyGraphicsServer.Rd +++ b/man/safetyGraphicsServer.Rd @@ -12,7 +12,8 @@ safetyGraphicsServer( mapping, domainData, charts, - filterDomain + filterDomain, + config ) } \arguments{ diff --git a/man/safetyGraphicsUI.Rd b/man/safetyGraphicsUI.Rd index 99b312a5..19bdbd6a 100644 --- a/man/safetyGraphicsUI.Rd +++ b/man/safetyGraphicsUI.Rd @@ -4,16 +4,18 @@ \alias{safetyGraphicsUI} \title{UI for the core safetyGraphics app including Home, Mapping, Filter, Charts and Settings modules.} \usage{ -safetyGraphicsUI(id, meta, domainData, mapping, standards) +safetyGraphicsUI(id, meta, mapping, domainData, charts, standards, config) } \arguments{ \item{id}{module ID} \item{meta}{data frame containing the metadata for use in the app.} +\item{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()}} + \item{domainData}{named list of data.frames to be loaded in to the app.} -\item{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()}} +\item{charts}{list of charts in the format produced by safetyGraphics::makeChartConfig()} \item{standards}{a list of information regarding data standards. Each list item should use the format returned by safetyGraphics::detectStandard.} } diff --git a/tests/testthat/test_getChartStatus.R b/tests/testthat/test_getChartStatus.R new file mode 100644 index 00000000..f0586ec4 --- /dev/null +++ b/tests/testthat/test_getChartStatus.R @@ -0,0 +1,92 @@ +context("Tests for the getChartStatus() function") +library(safetyGraphics) +library(safetyCharts) + +# Functional Specification +# [*] chart with no data spec throws error +# [*] status returned correctly for valid chart +# [*] status returned correctly when mapping values for a column are '' and NA +# [*] status returned correctly when dataSpec is not found in mapping + +test_that("chart with no data spec throws error",{ + expect_error(chart=list(domains=c("ae","dm"))) +}) + +test_that("status returned correctly for valid chart",{ + ae_chart <- list( + domains=c("aes","dm"), + dataSpec=list( + aes=c("id_col","custom_col"), + dm=c("id_col","test_col") + ) + ) + + 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") + ) + + status<-getChartStatus(chart=ae_chart, mapping=mapping) + expect_true(status$status) + expect_true(status$domains$aes) + expect_true(status$domains$dm) + expect_true(status$columns$aes$id_col) + expect_true(status$columns$aes$custom_col) + expect_true(status$columns$dm$id_col) + expect_true(status$columns$dm$test_col) +}) + + +test_that("status returned correctly for invalid chart",{ + ae_chart <- list( + domains=c("aes","dm"), + dataSpec=list( + aes=c("id_col","custom_col"), + dm=c("id_col","test_col") + ) + ) + + mapping <- data.frame( + domain=c("aes","aes","dm","dm"), + text_key=c("id_col","custom_col","id_col","test_col"), + current=c(NA,"","myID","dmCol") + ) + + status<-getChartStatus(chart=ae_chart, mapping=mapping) + expect_false(status$status) + expect_false(status$domains$aes) + expect_true(status$domains$dm) + expect_false(status$columns$aes$id_col) + expect_false(status$columns$aes$custom_col) + expect_true(status$columns$dm$id_col) + expect_true(status$columns$dm$test_col) +}) + + +test_that("status returned correctly for invalid chart",{ + ae_chart <- list( + domains=c("aes","dm"), + dataSpec=list( + aes=c("id_col","custom_col","another_col"), + dm=c("id_col","test_col") + ) + ) + + mapping <- data.frame( + domain=c("aes","aes","dm","dm"), + text_key=c("id_col","custom_col","id_col","test_col"), + current=c("myID","aesCol","myID","dmCol") + ) + ml<-generateMappingList(settingsDF=mapping, domain='aes') + expect_false(hasName(ml,"another_col")) + status<-getChartStatus(chart=ae_chart, mapping=mapping) + expect_false(status$status) + expect_false(status$domains$aes) + expect_true(status$domains$dm) + expect_true(status$columns$aes$id_col) + expect_true(status$columns$aes$custom_col) + expect_false(status$columns$aes$another_col) + expect_true(status$columns$dm$id_col) + expect_true(status$columns$dm$test_col) +})