diff --git a/DESCRIPTION b/DESCRIPTION index 476adb11..7dcb7056 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -43,6 +43,9 @@ Imports: haven, shinyWidgets, tidyr, - shinybusy + shinybusy, + listviewer, + shinyFiles, + rprojroot VignetteBuilder: knitr Roxygen: list(markdown = TRUE) diff --git a/NAMESPACE b/NAMESPACE index 9c9fb165..46dbda9d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,9 @@ # Generated by roxygen2: do not edit by hand +export(add_chart) export(app_server) export(app_ui) +export(chart_template) export(chartsNav) export(chartsRenderModule) export(chartsRenderModuleUI) @@ -11,6 +13,7 @@ export(chartsRenderWidget) export(chartsRenderWidgetUI) export(chartsTab) export(chartsTabUI) +export(create_new_safetyGraphics_app) export(detectStandard) export(evaluateStandard) export(filterTab) @@ -37,18 +40,32 @@ export(settingsMappingUI) import(clisymbols) import(dplyr) import(esquisse) +import(listviewer) import(magrittr) import(shiny) +import(shinyFiles) import(tools) import(yaml) importFrom(DT,DTOutput) importFrom(DT,renderDT) +importFrom(fs,dir_copy) +importFrom(fs,file_create) +importFrom(fs,file_exists) +importFrom(fs,path) +importFrom(fs,path_abs) importFrom(haven,read_sas) +importFrom(listviewer,jsonedit) +importFrom(listviewer,jsoneditOutput) +importFrom(listviewer,renderJsonedit) importFrom(magrittr,"%>%") importFrom(purrr,keep) importFrom(purrr,map) importFrom(purrr,transpose) importFrom(rlang,.data) +importFrom(rprojroot,find_root) +importFrom(rprojroot,is_rstudio_project) +importFrom(rstudioapi,isAvailable) +importFrom(rstudioapi,openProject) importFrom(shiny,dataTableOutput) importFrom(shiny,renderDataTable) importFrom(shinyWidgets,materialSwitch) @@ -60,4 +77,6 @@ importFrom(stringr,str_detect) importFrom(stringr,str_split) importFrom(stringr,str_to_upper) importFrom(tidyr,gather) +importFrom(usethis,create_project) importFrom(utils,globalVariables) +importFrom(yaml,write_yaml) diff --git a/R/add_chart.R b/R/add_chart.R new file mode 100644 index 00000000..c07778b6 --- /dev/null +++ b/R/add_chart.R @@ -0,0 +1,254 @@ + +#' Add chart config (adapted from golem::add_module()) +#' +#' +#' This function creates a module inside the local `config/` folder to define a new chart +#' - dump yaml +#' - create R scripts +#' +#' @param path Path to store the chart configuration files, expecting a config root folder +#' +#' +-- config +#'| +-- aeExplorer.yaml +#'| +-- newChart.yaml +#' +# | \-- workflow +#'| +-- aeExplorer_init.R +#'| +-- newChart_main.R +#' +#' @param name The name of the chart (also name of the yaml file) +#' @param label Label of chart +#' @param type Type of chart: `plot`, `module`, or `htmlwidget`. Default is `plot` (static) +#' @param domain associated data domain, for example `aes`, `labs`, or `multiple` +#' @param package optional, R package that this chart is associated with. +#' @param chart_template chart template function +#' +#' @seealso [chart_template()] +#' +#' @importFrom rprojroot find_root is_rstudio_project +#' @importFrom fs path file_create file_exists +#' @importFrom yaml write_yaml +#' +#' @export +add_chart <- function( + name = "newplot", + label = "New Static Plot", + type = "plot", + domain = "labs", + package = NULL, + workflow = list(), + open = TRUE, + ... +){ + + proj_root <- rprojroot::find_root(rprojroot::is_rstudio_project) + + path <- file.path(proj_root, "config") + + yaml_where <- file.path( + path, paste0(name, ".yaml") + ) + + + # label: Safety Explorer + # type: htmlwidget + # domain: multiple + # package: safetyCharts + # workflow: + # init: aeExplorer_init + # widget: aeExplorer + # + conf <- list() + + conf$label <- label + conf$type <- type + conf$domain <- domain + conf$package <- package + conf$workflow <- workflow + + + + if (tolower(type) == "module") { + conf$workflow <- list( + ui <- paste0(name, "_ui"), + server <- paste0(name, "_server") + ) + } else if (tolower(type) == "htmlwidget") { + #TODO add htmlwidget + } else if (tolower(type) == "plot") { + conf$workflow$main <- name + } + + if (!fs::file_exists(yaml_where)){ + write_yaml(conf, yaml_where) + } + + + r_where <- file.path( + path, + "workflow", + paste0(name, ".R") + ) + + if (!fs::file_exists(r_where)){ + fs::file_create(r_where) + chart_template(name = name, path = r_where, type=type, ...) + } +} + + +#' Chart Template Function +#' @inheritParams add_chart +#' @param path The path to the R script where the module will be written. +#' Note that this path will not be set by the user but internally by +#' `add_chart()`. +#' @param ... Arguments to be passed to the template, via `add_chart()` +#' +#' @return Used for side effect +#' @export +#' @seealso [add_chart()] +chart_template <- function(name, path, type, ...){ + + write_there <- function(...){ + write(..., file = path, append = TRUE) + } + + + if (type=="plot"){ + + # template_r <- system.file("config/workflow", "safety_histogram_chart.R", package = "safetyCharts") + # file.copy(from = template_r, to = path, overwrite = T) + + write_there(sprintf("%s <- function(data, settings){", name)) + + func_body <- + ' ## Replace with your custom code ## + params <- aes_( + x=as.name(settings$studyday_col), + y=as.name(settings$value_col), + group=as.name(settings$id_col) + ) + + + if(hasName(settings, "measure_values")){ + sub <- data %>% filter(!!sym(settings$measure_col) %in% settings$measure_values) + } else { + sub <- data + } + + p <- ggplot(data=sub, params) + + geom_path(color = "black", alpha=0.15) + + labs(x="Study Day", y="Lab Value", title="Lab Overview", subtitle="")+ + facet_grid( + rows=as.name(settings$measure_col), + scales="free_y" + ) + + theme_bw() + + return(p) + + ' + write_there(func_body) + write_there("}") + + } else if (type=="module"){ + + # write UI + write_there(sprintf("%s_ui <- function(id){", name)) + write_there(" ns <- NS(id)") + write_there(" tagList(") + + ph_ui <- ' + sidebar<-sidebarPanel( + selectizeInput( + ns("measures"), + "Select Measures", + multiple=TRUE, + choices=c("") + ) + ) + main<-mainPanel(plotOutput(ns("customModOutput"))) + ui<-fluidPage( + sidebarLayout( + sidebar, + main, + position = c("right"), + fluid=TRUE + ) + ) + return(ui) + ' + write_there(ph_ui) + write_there(" )") + write_there("}") + write_there(" ") + + # write server use pre shiny v1.5 module convention + write_there(sprintf("#' %s Server Function", name)) + write_there("#'") + write_there(sprintf("%s_server <- function(input, output, session, params){", name)) + write_there(" ns <- session$ns") + ph_server <- ' + ## replace with your custom code ## + # Populate control with measures and select all by default + observe({ + measure_col <- params()$settings$measure_col + measures <- unique(params()$data[[measure_col]]) + updateSelectizeInput( + session, + "measures", + choices = measures, + selected = measures + ) + }) + + # cusomize selected measures based on input + settingsR <- reactive({ + settings <- params()$settings + settings$measure_values <- input$measures + return(settings) + }) + + + #draw the chart + output$customModOutput <- renderPlot({ + + data <- params()$data + settings <- settingsR() + + params <- aes_( + x=as.name(settings$studyday_col), + y=as.name(settings$value_col), + group=as.name(settings$id_col) + ) + + + if(hasName(settings, "measure_values")){ + sub <- data %>% filter(!!sym(settings$measure_col) %in% settings$measure_values) + } else { + sub <- data + } + + p <- ggplot(data=sub, params) + + geom_path(color = "black", alpha=0.15) + + labs(x="Study Day", y="Lab Value", title="Lab Overview", subtitle="")+ + facet_grid( + rows=as.name(settings$measure_col), + scales="free_y" + ) + + theme_bw() + + return(p) + + }) + ' + write_there(ph_server) + write_there("}") + write_there(" ") + + + } else if (type=="htmlwidget"){ + ##TODO add htmlwidget chart template code + } + +} \ No newline at end of file diff --git a/R/app_init_addin.R b/R/app_init_addin.R new file mode 100644 index 00000000..87ce12de --- /dev/null +++ b/R/app_init_addin.R @@ -0,0 +1,215 @@ +#' RStudio Add-in for constructing lean ADLB and ADAE data +#' +#' +#' @import shiny +#' @import shinyFiles +#' @import listviewer +#' + + +app_init_addin <- function(){ + + + ui <- bootstrapPage( + + shiny::wellPanel( + h1("Choose Configs"), + shinyFiles::shinyDirButton('directory', label='Dir select', title='Please select a folder', multiple=FALSE), + shiny::verbatimTextOutput("directorypath") + ), + + + shiny::wellPanel( + h1("View and Edit Config"), + hr(), + + shiny::wellPanel( + h3("Edit Chart level config/Meta data"), + listviewer::reactjsonOutput( "rjed" ) + ), + hr(), + + shiny::wellPanel( + h3("Result config/chart meta data"), + DT::DTOutput("DTmeta") + ), + hr(), + + shiny::wellPanel( + h3("Add chart"), + splitLayout( + textInput("chart_name", label = "Chart Name or ID", value="new_chart"), + textInput("chart_label", label = "Chart Label", value="New Chart"), + selectizeInput("chart_type", "Type of chart", choices=c("plot", "module", "htmlwidget"), selected="plot"), + selectizeInput("chart_domain", "Choose data domain", choices=c("aes", "labs", "mutliple"), selected="labs"), + textInput("chart_package", "R package", value = "No Package"), + textInput("chart_path", "Path to chart yaml file") + ), + actionButton("addChartBtn", "Add chart") + ) + ), + + + tags$head( + tags$style(HTML(" + iframe + { + max-height: 200vh; + height:800px; + } + ")) + ), + + shiny::wellPanel( + h1("Preview App"), + uiOutput("app") + ) + + ) + + server <- function(input, output, session) { + + volumes <- c(wd=".", Home = fs::path_home(), "R Installation" = R.home(), shinyFiles::getVolumes()()) + + shinyFiles::shinyFileChoose(input, "file", roots = volumes, session = session) + shinyFiles::shinyDirChoose(input, "directory", roots = volumes, session = session, restrictions = system.file(package = "base"), allowDirCreate = FALSE) + + + local_rv <- reactiveValues() + + shiny::observeEvent(input$selectFile, { + + path <- rstudioapi::selectFile() + + local_rv$filesel <- file.info(path) + + }) + + + output$fileSelected <- renderPrint({ + + local_rv$filesel + }) + + output$filepaths <- renderPrint({ + if (is.integer(input$file)) { + cat("No files have been selected (shinyFileChoose)") + } else { + shinyFiles::parseFilePaths(volumes, input$file) + } + }) + + dirParsed <- reactive({ + shinyFiles::parseDirPath(volumes, input$directory) + }) + + output$directorypath <- renderPrint({ + if (is.integer(input$directory)) { + cat("No directory has been selected (shinyDirChoose)") + } else { + dirParsed() + } + }) + + # load config + configObj <- reactive({ + req(dirParsed()) + safetyGraphics::makeChartConfig( + dirs = dirParsed() + ) + + }) + + + safetyGraphicsApp1 <- function( + domainData=list( + labs=safetyGraphics::labs, + aes=safetyGraphics::aes, + dm=safetyGraphics::dm + ), + meta = safetyGraphics::meta, + charts=NULL, + mapping=NULL, + chartSettingsPaths = NULL + ){ + + config <- safetyGraphics:::app_startup(domainData, meta, charts, mapping, chartSettingsPaths) + + app <- shinyApp( + ui = app_ui(config$meta, config$domainData, config$mapping, config$standards), + server = app_server(input, output, session, config$meta, config$mapping, config$domainData, config$charts) + ) + } + + + output$app <- renderUI({ + req(input$rjed_edit) + #browser() + print(names(newConfig())) + tagList( + safetyGraphicsApp1(charts = newConfig()) + ) + }) + + + output$rjed <- listviewer::renderReactjson({ + req(configObj()) + listviewer::reactjson( configObj() ) + }) + + + oldConfig <- reactive({ + safetyGraphics::makeChartConfig(dirs=dirParsed()) + }) + + newConfig <- eventReactive(eventExpr = input$rjed_edit, valueExpr = { + + + newChartConfig <- input$rjed_edit$value$updated_src + + for (i in seq_along(names(newChartConfig))) { + + + chartFunctions <- newChartConfig[[i]]$functions + chartFunctionsNames <- names(chartFunctions) + + chartFunctions <- lapply(chartFunctionsNames, + function(cf) { + eval(parse(text=paste(unlist(chartFunctions[[cf]]), collapse = "\n")), + envir = .GlobalEnv) + }) + names(chartFunctions) <- chartFunctionsNames + newChartConfig[[i]]$functions <- chartFunctions + } + newChartConfig + }) + + tblMeta <- function(charts){ + + bbb <- purrr::map(charts, ~{ + bb <- as_tibble(t(tibble(.x))) + names(bb) <- names(.x) + bb + }) + + bbbb<- do.call(bind_rows, bbb) + + } + + + # DT for charts meta data + output$DTmeta <- DT::renderDT({ + tblMeta(newConfig()) + }) + + + + + } + + #viewer <- dialogViewer("SafetyApp initializer", width = 1200, height = 900) + viewer <- shiny::browserViewer(browser = getOption("browser")) + shiny::runGadget(ui, server, viewer = viewer ) +} + + diff --git a/R/create_new_safetyGraphics_app.R b/R/create_new_safetyGraphics_app.R new file mode 100644 index 00000000..e8f96781 --- /dev/null +++ b/R/create_new_safetyGraphics_app.R @@ -0,0 +1,69 @@ +#' start new project with an instance of safetyGraphicsApp() +#' @param path location for new safetyGraphicsApp +#' @param init_default_configs copy over `safetyCharts` default configs? +#' @param open open new rstudio project? +#' +#' @return Used for side effect +#' +#' @importFrom rstudioapi isAvailable openProject +#' @importFrom fs path_abs path dir_copy +#' @importFrom usethis create_project +#' +#' +#' @export +#' + +create_new_safetyGraphics_app <- function( + path, + init_default_configs, + open = TRUE, + gui = FALSE +) { + + path <- fs::path_abs(path) + + if(init_default_configs){ + from_path <- system.file("config", package = "safetyCharts") + fs::dir_copy(path=from_path, new_path = file.path(path, "config"), overwrite = TRUE) + } + + # write start_app.R + cat( + ' + # load required libraries + library(safetyCharts) + library(safetyGraphics) + + # Start default App + safetyGraphics::safetyGraphicsApp() + + # Run the RStudio app initialization Addin + + ## Option 1: run the following line of code + safetyGraphics:::app_init_addin() + + ## Option 2: open through RStudo Addin button above + + + # You can scaffold a new chart by calling the add_chart function. see ?add_chart for details + safetyGraphics::add_chart("newChart", "my new chart") + + # That is it for now! + ', + file= file.path(path, "start_app.R") + ) + + + if (gui == FALSE) { + # create rstudio project + usethis::create_project(path = path, open = open) + } + + return(invisible(path)) +} + +# to be used in RStudio "new project" GUI +create_new_safetyGraphics_app_gui <- function(path,...){ + dots <- list(...) + create_new_safetyGraphics_app(path, dots$check_default_configs, open = FALSE, gui=TRUE) +} \ No newline at end of file diff --git a/R/mod_settingsCharts.R b/R/mod_settingsCharts.R index 450bba92..3b41b8c6 100644 --- a/R/mod_settingsCharts.R +++ b/R/mod_settingsCharts.R @@ -1,13 +1,18 @@ #' @title Settings Module - chart details #' @description Settings Module - sub-module showing details for the charts loaded in the app - UI #' +#' @importFrom listviewer renderJsonedit jsonedit jsoneditOutput #' @export settingsChartsUI <- function(id){ ns <- NS(id) list( h1("Chart Metadata"), - verbatimTextOutput(ns("chartList"))) + tabsetPanel( + tabPanel("jsonedit View", jsoneditOutput(ns("chartObj"), height = "800px") ), + tabPanel("DT format", DT::DTOutput(ns("chartMetaDT"))), + tabPanel("Verbatim", verbatimTextOutput(ns("chartList")))) + ) } #' @title Settings Module - charts details - server @@ -22,7 +27,31 @@ settingsChartsUI <- function(id){ settingsCharts <- function(input, output, session, charts){ ns <- session$ns + + output$chartObj <- listviewer::renderJsonedit({ + listviewer::jsonedit(charts) + }) output$chartList <- renderPrint({ print(charts) }) + + + tblMeta <- function(charts){ + #TODO move this function to a helper file and fix warning messages + bbb <- purrr::map(charts, ~{ + bb <- as_tibble(t(tibble(.x)), .name_repair="unique") + names(bb) <- names(.x) + bb + }) + + bbbb<- do.call(bind_rows, bbb) + + } + + + # DT for charts meta data + output$chartMetaDT <- DT::renderDT({ + DT::datatable( tblMeta(charts) ) + }) + } \ No newline at end of file diff --git a/data/meta.rda b/data/meta.rda index e8c20b29..5dfc94d5 100644 Binary files a/data/meta.rda and b/data/meta.rda differ diff --git a/inst/rstudio/addins.dcf b/inst/rstudio/addins.dcf new file mode 100644 index 00000000..2ba69611 --- /dev/null +++ b/inst/rstudio/addins.dcf @@ -0,0 +1,5 @@ +Name: Add Safety Chart +Description: Add new custom chart +Binding: app_init_addin +Interactive: false + diff --git a/inst/rstudio/templates/project/create_safetyGraphics_app.dcf b/inst/rstudio/templates/project/create_safetyGraphics_app.dcf new file mode 100644 index 00000000..5e9b85de --- /dev/null +++ b/inst/rstudio/templates/project/create_safetyGraphics_app.dcf @@ -0,0 +1,11 @@ +Binding: create_new_safetyGraphics_app_gui +Title: Create new safetyGraphics app project +OpenFiles: start_app.R +Icon: safetyGraphicsHex.png + + +Parameter: check_default_configs +Widget: CheckboxInput +Label: Start with safetyCharts default configs? +Default: On +Position: left \ No newline at end of file diff --git a/inst/rstudio/templates/project/safetyGraphicsHex.png b/inst/rstudio/templates/project/safetyGraphicsHex.png new file mode 100644 index 00000000..47b0c624 Binary files /dev/null and b/inst/rstudio/templates/project/safetyGraphicsHex.png differ diff --git a/man/add_chart.Rd b/man/add_chart.Rd new file mode 100644 index 00000000..4ec40b57 --- /dev/null +++ b/man/add_chart.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_chart.R +\name{add_chart} +\alias{add_chart} +\title{Add chart config (adapted from golem::add_module())} +\usage{ +add_chart( + name = "newplot", + label = "New Static Plot", + type = "plot", + domain = "labs", + package = NULL, + workflow = list(), + open = TRUE, + ... +) +} +\arguments{ +\item{name}{The name of the chart (also name of the yaml file)} + +\item{label}{Label of chart} + +\item{type}{Type of chart: \code{plot}, \code{module}, or \code{htmlwidget}. Default is \code{plot} (static)} + +\item{domain}{associated data domain, for example \code{aes}, \code{labs}, or \code{multiple}} + +\item{package}{optional, R package that this chart is associated with.} + +\item{path}{Path to store the chart configuration files, expecting a config root folder + ++-- config +| +-- aeExplorer.yaml +| +-- newChart.yaml + +| +-- aeExplorer_init.R +| +-- newChart_main.R} + +\item{chart_template}{chart template function} +} +\description{ +This function creates a module inside the local \verb{config/} folder to define a new chart +\itemize{ +\item dump yaml +\item create R scripts +} +} +\seealso{ +\code{\link[=chart_template]{chart_template()}} +} diff --git a/man/app_init_addin.Rd b/man/app_init_addin.Rd new file mode 100644 index 00000000..c88f9e5e --- /dev/null +++ b/man/app_init_addin.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/app_init_addin.R +\name{app_init_addin} +\alias{app_init_addin} +\title{RStudio Add-in for constructing lean ADLB and ADAE data} +\usage{ +app_init_addin() +} +\description{ +RStudio Add-in for constructing lean ADLB and ADAE data +} diff --git a/man/chart_template.Rd b/man/chart_template.Rd new file mode 100644 index 00000000..a77004d6 --- /dev/null +++ b/man/chart_template.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_chart.R +\name{chart_template} +\alias{chart_template} +\title{Chart Template Function} +\usage{ +chart_template(name, path, type, ...) +} +\arguments{ +\item{name}{The name of the chart (also name of the yaml file)} + +\item{path}{The path to the R script where the module will be written. +Note that this path will not be set by the user but internally by +\code{add_chart()}.} + +\item{type}{Type of chart: \code{plot}, \code{module}, or \code{htmlwidget}. Default is \code{plot} (static)} + +\item{...}{Arguments to be passed to the template, via \code{add_chart()}} +} +\value{ +Used for side effect +} +\description{ +Chart Template Function +} +\seealso{ +\code{\link[=add_chart]{add_chart()}} +} diff --git a/man/create_new_safetyGraphics_app.Rd b/man/create_new_safetyGraphics_app.Rd new file mode 100644 index 00000000..545c32e6 --- /dev/null +++ b/man/create_new_safetyGraphics_app.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/create_new_safetyGraphics_app.R +\name{create_new_safetyGraphics_app} +\alias{create_new_safetyGraphics_app} +\title{start new project with an instance of safetyGraphicsApp()} +\usage{ +create_new_safetyGraphics_app( + path, + init_default_configs, + open = TRUE, + gui = FALSE +) +} +\arguments{ +\item{path}{location for new safetyGraphicsApp} + +\item{init_default_configs}{copy over \code{safetyCharts} default configs?} + +\item{open}{open new rstudio project?} +} +\value{ +Used for side effect +} +\description{ +start new project with an instance of safetyGraphicsApp() +}