diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 2d937cb..ab17c14 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 - + - name: Compile rstan files via rstantools run: | # R is already installed in the base-image @@ -49,7 +49,7 @@ jobs: # Save the generated site to be shared with the next job - name: Upload site artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: site path: docs @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v4 - name: Download site artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: site path: docs diff --git a/DESCRIPTION b/DESCRIPTION index a66d834..fb467a6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.10.0 +Version: 25.02.0.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -13,19 +13,13 @@ LazyData: true ByteCompile: true Depends: R (>= 3.5.0), Rcpp (>= 0.12.0) Imports: - colourpicker, + ChangeR (>= 24.11.0.4), dplyr, ggplot2 (>= 2.2.1), - DataTools (>= 23.12.2.3), + DataTools (>= 24.10.0.2), futile.logger, - htmltools, - jsonlite, - loo, - magrittr, - mcp, methods, modules, - openxlsx, parallel, rlang, rstan (>= 2.18.1), @@ -36,7 +30,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 24.10.0), + shinyTools (>= 25.02.0.2), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: diff --git a/NAMESPACE b/NAMESPACE index d9616bd..768c0ef 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,17 +7,12 @@ export(config) export(defaultInputsForUI) export(estimateIntervals) export(estimateTimePoint) -export(exportCSV) -export(exportFilename) -export(exportJSON) -export(exportXLSX) export(extractIndividuals) export(extractModelOutputs) export(extractSavedModels) export(getEntry) export(getExampleDataMatrix) export(getExampleIsotopeVals) -export(getHelp) export(getSeed) export(getShiftTime) export(getSiteStayTimes) @@ -45,6 +40,8 @@ import(methods) import(rstantools) import(shiny) import(shinythemes) +importFrom(ChangeR,changePointsServer) +importFrom(ChangeR,changePointsUI) importFrom(DataTools,checkAnyNonNumericColumns) importFrom(DataTools,downloadModelServer) importFrom(DataTools,downloadModelUI) @@ -54,7 +51,7 @@ importFrom(DataTools,importOptions) importFrom(DataTools,importServer) importFrom(DataTools,importUI) importFrom(DataTools,renameExistingNames) -importFrom(colourpicker,colourInput) +importFrom(dplyr,"%>%") importFrom(dplyr,arrange) importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) @@ -87,23 +84,17 @@ importFrom(ggplot2,scale_x_continuous) importFrom(ggplot2,scale_y_continuous) importFrom(ggplot2,sec_axis) importFrom(ggplot2,theme) -importFrom(htmltools,save_html) -importFrom(jsonlite,toJSON) -importFrom(loo,loo) -importFrom(loo,loo_compare) -importFrom(loo,waic) -importFrom(magrittr,"%>%") -importFrom(mcp,mcp) -importFrom(openxlsx,write.xlsx) importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) importFrom(rstan,sampling) +importFrom(shinyTools,addCustomPointsToGGplot) importFrom(shinyTools,calculateRescalingFactors) +importFrom(shinyTools,customPointsServer) +importFrom(shinyTools,customPointsUI) importFrom(shinyTools,dataExportButton) importFrom(shinyTools,dataExportServer) importFrom(shinyTools,extractTitle) -importFrom(shinyTools,formatLegendOfGGplot) importFrom(shinyTools,formatPointsOfGGplot) importFrom(shinyTools,formatScalesOfGGplot) importFrom(shinyTools,formatTitlesOfGGplot) @@ -117,6 +108,7 @@ importFrom(shinyTools,plotRangesServer) importFrom(shinyTools,plotRangesUI) importFrom(shinyTools,plotTitlesServer) importFrom(shinyTools,plotTitlesUI) +importFrom(shinyTools,setLegendThemeOfGGplot) importFrom(shinyTools,shinyTryCatch) importFrom(shinyWidgets,pickerInput) importFrom(shinyWidgets,updatePickerInput) @@ -125,7 +117,6 @@ importFrom(shinyjs,alert) importFrom(shinyjs,enable) importFrom(shinyjs,runjs) importFrom(stats,approx) -importFrom(stats,as.formula) importFrom(stats,dnorm) importFrom(stats,lm) importFrom(stats,median) @@ -133,8 +124,6 @@ importFrom(stats,na.omit) importFrom(stats,quantile) importFrom(stats,sd) importFrom(utils,combn) -importFrom(utils,read.csv) importFrom(utils,write.csv) -importFrom(utils,write.table) importFrom(yaml,read_yaml) useDynLib(OsteoBioR, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index df362ee..71435d4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# OsteoBioR 25.02.0 + +## New Features +- option to add custom points to a plot +- option to use custom legend labels for the plot (#69) +- new logos and links in header + +# OsteoBioR 24.11.0 + +## Updates +- integration of modules from _ChangeR_ package for break-point detection (#72) +- option to set the "x" and "y" columns for mcp modelling + # OsteoBioR 24.10.0 ## Updates diff --git a/R/01-plotFormatting.R b/R/01-plotFormatting.R index 64552db..bf62b78 100644 --- a/R/01-plotFormatting.R +++ b/R/01-plotFormatting.R @@ -1,32 +1,36 @@ getStyleForIndividuals <- function(pointStyleList, input) { # pointStyleList are reactive values -> lapply over the names and not(!) the list itself - style <- lapply(names(pointStyleList), function(x) {pointStyleList[[x]][input]}) %>% + style <- lapply(names(pointStyleList), function(individual) {pointStyleList[[individual]][input]}) %>% unlist() names(style) <- names(pointStyleList) style } -getDefaultPointFormatForModels <- function(pointStyleList, modelNames) { +# Get the default point format for models +# +# @param pointStyleList list of point styles for each model +# @param modelNames names of the models +# @param isFullReset if TRUE, all point styles are reset to the default values +getDefaultPointFormatForModels <- function(pointStyleList, modelNames, isFullReset = FALSE) { # default colours defaultColours <- ggplot2::scale_color_discrete()$palette(n = length(modelNames)) names(defaultColours) <- modelNames # setup lists with default values for style specs for (i in modelNames) { - if (is.null(pointStyleList[[i]])) + if (is.null(pointStyleList[[i]]) || isFullReset) { pointStyleList[[i]] <- config()[["defaultPointStyle"]][["dataPoints"]] - # use default colour per model - pointStyleList[[i]]["color"] <- defaultColours[i] + # use default colour per model + pointStyleList[[i]]["color"] <- defaultColours[i] + } } return(pointStyleList) } getDefaultTextFormat <- function() { - list(legendTitle = config()[["defaultIntervalTimePlotTitle"]], - legendText = config()[["defaultIntervalTimePlotText"]], - plotTitle = config()[["defaultIntervalTimePlotTitle"]], + list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], xAxisTitle = config()[["defaultIntervalTimePlotTitle"]], yAxisTitle = config()[["defaultIntervalTimePlotTitle"]], yAxisTitle2 = config()[["defaultIntervalTimePlotTitle"]], diff --git a/R/02-breakPoints.R b/R/02-breakPoints.R deleted file mode 100644 index ed353a6..0000000 --- a/R/02-breakPoints.R +++ /dev/null @@ -1,266 +0,0 @@ -#' Get Example Matrix -#' -#' @param path path to example matrix -readExampleMatrix <- function(path) { - df <- read.csv(path) - - df[is.na(df)] <- "" - - res <- df %>% as.matrix() - colnames(res) <- 1:ncol(res) - rownames(res) <- 1:nrow(res) - - res -} - -#' Get the comb matrix -#' -#' Get the combined matrix of segments and priors. -#' Process the segments and priors matrices, identify relevant rows and columns, concatenate them, -#' and generate possible cell combinations while ensuring data consistency. -#' -#' @param segments A matrix containing the segmented formulas -#' @param priors A matrix containing the priors -#' -#' @return A matrix containing the segmented formulas and priors -getComb <- function(segments, priors) { - # find rows and columns in segments that are not all "" - row_indices <- apply(segments, 1, function(x) - any(x != "")) - col_indices <- apply(segments, 2, function(x) - any(x != "")) - - # subset segments and priors based on these indices - segments <- segments[row_indices, col_indices, drop = FALSE] - priors <- priors[row_indices, col_indices, drop = FALSE] - - # concat matrices - concatenated_matrix <- matrix( - paste(segments, priors, sep = "*+*"), - nrow = nrow(segments), - ncol = ncol(segments) - ) - - #SET: change number of concatenated_matrix below so that it matches the number of rows s_rows - - # List all possible cell combinations among the matrix considering only cells in different columns and must follow column order - - col_list <- lapply(1:ncol(segments), function(x) - concatenated_matrix[, x]) - - comb <- do.call(expand.grid, col_list) - - # Data conversion to avoid warnings - - comb[] <- lapply(comb, as.character) - - comb -} - -#' Clean the comb matrix -#' -#' Check each row for '*+*' and if found replace that cell and all following cells in the row with "". -#' Remove Blank Rows. Remove duplicate rows. -#' -#' @param comb A matrix containing the segmented formulas and priors -#' -#' @return A cleaned matrix -cleanComb <- function(comb) { - n_rows <- nrow(comb) - - # Check each row for '*+*' and if found replace that cell and all following cells in the row with "" - for (i in 1:n_rows) { - # Get the indices of the cells equal to "*+*" - replace_indices <- which(comb[i, ] == "*+*") - - # if '*+*' is found in the row replace it and following elements with "" - if (length(replace_indices) > 0) { - comb[i, replace_indices[1]:ncol(comb)] <- "" - } - } - - # Remove Blank Rows - comb <- comb[apply(comb, 1, function(x) - any(x != "")), , drop = FALSE] - - # Remove duplicate rows - comb <- unique(comb) - - comb -} - -#' Split the comb matrix into two matrices -#' -#' Split the comb matrix into two matrices based on the separator '*+*'. -#' -#' @param comb A matrix containing the segmented formulas and priors -#' -#' @return A list of two matrices -splitComb <- function(comb) { - # Create two empty matrices with the same dimensions as comb - mat1 <- matrix(ncol = ncol(comb), nrow = nrow(comb)) - mat2 <- matrix(ncol = ncol(comb), nrow = nrow(comb)) - - # Write a loop to iterate through each cell of comb - for (i in seq_len(nrow(comb))) { - for (j in seq_len(ncol(comb))) { - # Check if "*+*" is in the cell value - if (grepl("\\+", as.character(comb[i, j]))) { - # Split the cell value using the separator "*+*" - split_vals <- strsplit(as.character(comb[i, j]), split = "\\*\\+\\*")[[1]] - - # If "*+*" is found, split the cell value into two, assigning each part to the corresponding cell in mat1 and mat2 - mat1[i, j] <- split_vals[1] - mat2[i, j] <- split_vals[2] - - } else { - # If "*+*" is not found in the cell value, assign "" to the corresponding cells in mat1 and mat2 - mat1[i, j] <- "" - mat2[i, j] <- "" - } - } - } - - # Replacing NA values with empty string - mat1[is.na(mat1)] <- "" - mat2[is.na(mat2)] <- "" - - # Return the two matrices - list(mat1 = mat1, mat2 = mat2) -} - -#' Set formulas and priors -#' -#' @param splittedComb A list of two matrices containing the segmented formulas and priors -#' -#' @return A list of lists containing the segmented formulas and priors -setFormulasAndPriors <- function(splittedComb) { - mat1 <- splittedComb$mat1 - mat2 <- splittedComb$mat2 - - # Creating lists to hold all the lists - lists_seg <- vector("list", nrow(mat1)) - lists_prior <- vector("list", nrow(mat1)) - - # Looping to convert each string in the matrix to a formula and adding to the respective list. - - for (i in 1:nrow(mat1)) { - lists_seg[[i]] <- list() - lists_prior[[i]] <- list() - - for (j in 1:ncol(mat1)) { - if (mat1[i, j] != "") { - lists_seg[[i]] <- append(lists_seg[[i]], as.formula(mat1[i, j])) - - # For priors - if (mat2[i, j] != "") { - # first split string by commas corresponding to different priors - splits <- strsplit(mat2[i, j], split = ";")[[1]] - - for (k in 1:length(splits)) { - # split the string by = - split_str <- strsplit(splits[k], "=")[[1]] - - if (!is.na(split_str[1]) && !is.na(split_str[2])) { - lists_prior[[i]] <- append(lists_prior[[i]], trimws(split_str[2])) - names(lists_prior[[i]])[length(lists_prior[[i]])] <- trimws(split_str[1]) - } - } - } - } - } - - } - - list(lists_seg = lists_seg, lists_prior = lists_prior) -} - -#' Run mcp model -#' -#' @param lists A list of lists containing the segmented formulas and priors -#' @param ... Additional arguments to pass to mcp -#' -#' @return A list of mcp model fits -runMcp <- function(lists, ...) { - lists_seg <- lists$lists_seg - lists_prior <- lists$lists_prior - - # Loop through each list and run model - fit <- vector(mode = "list", length = length(lists_seg)) - - for (i in 1:length(lists_seg)) { - fit[[i]] <- mcp(model = lists_seg[[i]], prior = lists_prior[[i]], ...) - } - - fit -} - -#' Compare models using loo -#' -#' @param fit A list of mcp model fits -#' -#' @return A loo model comparison object -compareWithLoo <- function(fit) { - #Define list - loo_model <- vector("list", length(fit)) - - for (i in 1:length(fit)) { - loo_model[[i]] <- loo(fit[[i]]) - } - - #Results of model comparison - loo_compare(loo_model) -} - -#' Compare models using waic -#' -#' @param fit A list of mcp model fits -#' -#' @return A loo model comparison object -compareWithWAIC <- function(fit) { - #Define list - waic_model <- vector("list", length(fit)) - - for (i in 1:length(fit)) { - waic_model[[i]] <- waic(fit[[i]]) - } - - #Results of model comparison - loo_compare(waic_model) -} - -#' Compare models using heuristic -#' -#' The heuristic method is a model comparison method that combines the number of parameters and the elpd_diff value. -#' -#' @param fit A list of mcp model fits -#' -#' @return A loo model comparison object -compareWithHeuristic <- function(fit) { - comparison <- compareWithLoo(fit) - - row_names <- rownames(comparison) - model_number <- as.integer(gsub(".*model([0-9]*).*", "\\1", row_names)) - numEntries <- array(0, nrow(comparison)) - k <- 1 - for (i in model_number) { - numEntries[k] <- length(unlist(fit[[i]][3])) - k <- k + 1 - } - - comparison <- cbind(comparison, numEntries) - # calculate the ratio and use ifelse function to assign zero if division by zero occurs - new_column <- abs(ifelse(comparison[,2] != 0, comparison[,1]/comparison[,2], 0)) - comparison <- cbind(comparison, new_column) - - # Remove rows where the last column value is bigger than 5 - comparison <- comparison[comparison[,10] < 5, , drop = FALSE] - - #Order by lowest number of model parameters and for equal number of parameters by higher elpd_diff value - if (nrow(comparison) > 1) { - comparison <- comparison[order(comparison[,9], -comparison[,1]),] - comparison <- cbind(comparison, "Rank" = 1:nrow(comparison)) - } - - comparison -} diff --git a/R/02-matrixModule.R b/R/02-matrixModule.R deleted file mode 100644 index c1f17a1..0000000 --- a/R/02-matrixModule.R +++ /dev/null @@ -1,187 +0,0 @@ -#' Matrix Module -#' -#' @param title title of the matrix UI -#' @param maxRows maximum number of rows -#' @param maxColumns maximum number of columns -#' @param defaultCellContent default cell content -#' @param exampleLabel example label -#' @rdname matrixServer -matrixUI <- function(id, - title = "Matrix", - maxRows = 10, - maxColumns = 5, - defaultCellContent = "", - exampleLabel = "Example Matrix") { - ns <- NS(id) - tagList( - if (!is.null(title)) - tags$h4(title) - else - NULL, - fluidRow( - column( - width = 2, - selectInput( - ns("rows"), - "No. of rows", - selected = 1, - choices = 1:maxRows - ) - ), - column( - width = 2, - selectInput( - ns("cols"), - "No. of columns", - selected = 1, - choices = 1:maxColumns - ) - ), - column( - width = 2, - selectInput( - ns("cellID"), - "Cell ID (row, column)", - selected = 1, - choices = 1:1 - ) - ), - column( - width = 3, - textInput(ns("cell"), "Cell content", value = defaultCellContent) - ), - column( - width = 1, - style = "margin-top: 1.75em;", - actionButton(ns("set"), "Set") - ), - column( - width = 2, - align = "right", - style = "margin-top: 1.75em;", - actionButton(ns("example"), exampleLabel) - ) - ), - # output matrix - tags$br(), - tableOutput(ns("outputMatrix")) - ) -} - -#' Matrix Server -#' -#' @param id namespace id -#' @param exampleFunction function to load example input -#' @param validateCellFunction function to validate the cell content -#' @param ... additional arguments to example function -matrixServer <- function(id, - exampleFunction, - validateCellFunction = NULL, - ...) { - if (is.null(validateCellFunction)) { - validateCellFunction <- function(x) - x - } - moduleServer(id, function(input, output, session) { - dataMatrix <- reactiveVal() - - observe({ - # define empty matrix with number of rows and columns inputs - req(input[["rows"]], input[["cols"]]) - nrow <- input[["rows"]] %>% as.numeric() - ncol <- input[["cols"]] %>% as.numeric() - new_matrix <- matrix( - data = "", - nrow = nrow, - ncol = ncol, - byrow = TRUE - ) - colnames(new_matrix) <- 1:ncol - rownames(new_matrix) <- 1:nrow - dataMatrix(new_matrix) - updateSelectInput(session, "cellID", choices = getCellChoices(nrow = nrow, ncol = ncol)) - }) %>% - bindEvent(list(input[["rows"]], input[["cols"]])) - - output$outputMatrix <- renderTable({ - validate(need( - dataMatrix(), - "Please click 'Set' first or load 'Example Priors'" - )) - dataMatrix() - }, rownames = TRUE) - - observe({ - req(input[["cellID"]], input[["cell"]], length(dataMatrix())) - # set new value - id <- as.numeric(input[["cellID"]]) - new_matrix <- dataMatrix() - - { - new_matrix[id] <- input[["cell"]] %>% - validateCellFunction() - } %>% - shinyTryCatch(errorTitle = "Error in syntax") - dataMatrix(new_matrix) - - # inc id - maxID <- length(dataMatrix()) - newID <- ifelse(id == maxID, 1, id + 1) - updateSelectInput(session, "cellID", selected = newID) - }) %>% - bindEvent(input[["set"]]) - - observe({ - newMatrix <- exampleFunction(...) - dataMatrix(newMatrix) - updateSelectInput(session, "cellID", choices = getCellChoices( - nrow = nrow(newMatrix), - ncol = ncol(newMatrix) - )) - updateTextInput(session, "cell", value = newMatrix[1]) - }) %>% - bindEvent(input[["example"]]) - - return(dataMatrix) - }) -} - -#' Get cell choices -#' -#' @param nrow Number of rows -#' @param ncol Number of columns -#' -#' @return A named vector of cell choices -getCellChoices <- function(nrow, ncol) { - getCellLabel <- function(cellID, matrixDim) { - cellInd <- arrayInd(cellID, matrixDim) - - sprintf("(%s, %s)", cellInd[1], cellInd[2]) - } - - choices <- 1:(nrow * ncol) - - # get ID labels - labels <- sapply(choices, getCellLabel, matrixDim = c(nrow, ncol)) - names(choices) <- labels - - choices -} - -#' Check formula syntax -#' -#' Check if the formula syntax is valid -#' -#' @param formula A formula to check -#' -#' @return A formula if valid, otherwise an error -validateFormula <- function(formula) { - formula_check <- try(as.formula(formula), silent = TRUE) - - if (inherits(formula_check, "try-error")) { - warning("No formula, invalid syntax. Please check your input!") - return(formula) - } else { - return(formula) - } -} diff --git a/R/03-breakPointDetection.R b/R/03-breakPointDetection.R deleted file mode 100644 index 81c693e..0000000 --- a/R/03-breakPointDetection.R +++ /dev/null @@ -1,512 +0,0 @@ -#' Break Point Detection UI -#' -#' UI of the module -#' -#' @rdname breakPointDetectionServer -breakPointDetectionUI <- function(id) { - ns <- NS(id) - tagList(tags$br(), - tabsetPanel( - id = ns("breakPointTabs"), - tabPanel("1. MCP Lists from Segments & Priors", mcpFormulasUI(ns("formulas"))), - tabPanel( - "2. MCP Modeling", - mcpModelingUI(ns("mcp")), - mcpShowSingleModelUI(ns("singleModelOut")) - ), - tabPanel("3. Comparison of Models", mcpCompareModelsUI(ns( - "compareModelsOut" - ))) - ), - tags$br()) -} - -#' Break Point Detection Server -#' -#' Server function of the module -#' -#' @param id The module id -#' @param plotData The reactive plot data -breakPointDetectionServer <- function(id, plotData) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - mcpData <- reactiveVal() - - observe({ - req(nrow(plotData()) > 0) - # select relevant columns - newData <- plotData()[, c("time", "median")] - # rename columns - colnames(newData) <- c("x", "y") - # remove rows with NA - newData <- na.omit(newData) - - mcpData(newData) - }) %>% bindEvent(plotData()) - - # example data instead of plot data - # observe({ - # mcpData(read.csv(file.path("data", "example_breakPoints.csv"))) - # }) %>% - # bindEvent(input[["mcp-loadExampleDf"]]) - - formulasAndPriors <- mcpFormulasServer(id = "formulas") - - mcpFitList <- mcpModelingServer(id = "mcp", - mcpData = mcpData, - formulasAndPriors = formulasAndPriors) - - mcpShowSingleModelServer( - id = "singleModelOut", - mcpData = mcpData, - formulasAndPriors = formulasAndPriors, - mcpFitList = mcpFitList - ) - - mcpCompareModelsServer( - id = "compareModelsOut", - mcpData = mcpData, - formulasAndPriors = formulasAndPriors, - mcpFitList = mcpFitList - ) - }) -} - - -# 1. MCP Segments & Priors ---- - -#' MCP Formulas UI -#' -#' @rdname mcpFormulasServer -mcpFormulasUI <- function(id) { - ns <- NS(id) - tagList( - tags$br(), - fluidRow(column(8, tags$h4("Segments")), column( - 4, align = "right", infoButtonUI(ns("formulaInfo"), label = "Segment Description") - )), - matrixUI( - ns("segments"), - title = NULL, - defaultCellContent = "y ~ 1 + x", - exampleLabel = "Example Segments" - ), - tags$br(), - fluidRow(column(8, tags$h4("Priors")), column( - 4, align = "right", infoButtonUI(ns("PriorInfo"), label = "Prior Description") - )), - matrixUI( - ns("priors"), - title = NULL, - defaultCellContent = "x_1 = dunif(-4, -0.5);", - exampleLabel = "Example Priors" - ), - tags$br(), - fluidRow( - column(10, verbatimTextOutput(ns("mcpFormulas"))), - column( - 2, - align = "right", - actionButton(ns("apply"), "Create MCP Lists", disabled = TRUE) - ) - ) - ) -} - -#' MCP Formulas Server -#' -#' @param id The module id -mcpFormulasServer <- function(id) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - formulasAndPriors <- reactiveVal() - - segmentsMatrix <- matrixServer( - "segments", - exampleFunction = readExampleMatrix, - validateCellFunction = validateFormula, - path = file.path("data", "example_breakPointSegments.csv") - ) - priorsMatrix <- matrixServer( - "priors", - exampleFunction = readExampleMatrix, - path = file.path("data", "example_breakPointPriors.csv") - ) - - infoButtonServer( - id = "formulaInfo", - title = "'Formula' (segment) description", - text = "The 'formula' argument in mcp defines the model structure. It specifies the relationship between - variables and the change points in your data. The formula should be written similarly to formulas - in base R, with additional support for change points.", - link = "https://lindeloev.github.io/mcp/articles/formulas.html" - ) - - infoButtonServer( - id = "PriorInfo", - title = "'Prior' description", - text = "The 'prior' argument in mcp specifies the prior distributions for the parameters in your model. - It should be a named list where the names correspond to the model parameters, - and the values are prior distributions in JAGS notation. If you don't specify a prior for a parameter, - mcp will use a default weakly informative prior.", - link = "https://lindeloev.github.io/mcp/articles/priors.html" - ) - - observe({ - req(segmentsMatrix(), priorsMatrix()) - - # enable the 'Create MCP Formulas' button - shinyjs::enable(ns("apply"), asis = TRUE) - #updateActionButton(session, "apply", disabled = FALSE) # not working with current version in docker - }) %>% bindEvent(list(segmentsMatrix(), priorsMatrix())) - - observe({ - newFormulasAndPriors <- getComb(segments = segmentsMatrix(), priors = priorsMatrix()) %>% - cleanComb() %>% - splitComb() %>% - setFormulasAndPriors() %>% - shinyTryCatch(errorTitle = "Error in creating MCP lists", warningTitle = "Warning in creating MCP lists") - - formulasAndPriors(newFormulasAndPriors) - }) %>% - bindEvent(input[["apply"]]) - - output$mcpFormulas <- renderPrint({ - validate(need( - formulasAndPriors(), - "Please 'Set' Segments and Priors first ..." - )) - formulasAndPriors() - }) - - return(formulasAndPriors) - }) -} - -#' Info Button UI -#' -#' @param label The label of the button -#' @rdname infoButtonServer -infoButtonUI <- function(id, label = "Description") { - ns <- NS(id) - tagList(actionButton(ns("show_info"), icon = icon("info-circle"), label)) -} - -#' Info Button Server -#' -#' @param id The module id -#' @param title The title of the modal -#' @param text The text to display in the modal -#' @param link The link to the documentation -infoButtonServer <- function(id, - title = "Description", - text, - link = NULL) { - moduleServer(id, function(input, output, session) { - observe({ - showModal( - modalDialog( - title = title, - p(text), - # Add a hyperlink to the github help page - if (!is.null(link)) { - p( - "For more details, visit the", - a("documentation", href = link, target = "_blank"), - "." - ) - } else - NULL, - footer = modalButton("Close"), - easyClose = TRUE - ) - ) - }) %>% - bindEvent(input$show_info) - }) -} - - -# 2. MCP Modeling ---- - -#' MCP Modeling UI -#' -#' @rdname mcpModelingServer -mcpModelingUI <- function(id) { - ns <- NS(id) - tagList(tags$br(), - fluidRow( - column( - 3, - numericInput(ns("adapt"), "Burn in length", value = 5000), - helpText("Increase for better conversion (makes the run slower).") - ), - column(3, numericInput( - ns("chains"), - "Number of chains", - value = 3, - min = 1 - )), - column( - 3, - numericInput( - ns("iter"), - "Number of iterations", - value = 3000, - min = 1 - ) - ), - column( - 3, - align = "right", - style = "margin-top: 1.75em;", - # load example data instead of plot data: - #actionButton(ns("loadExampleDf"), "Load Example Data"), - actionButton(ns("apply"), "Run MCP", disabled = TRUE) - ) - ), - tags$hr()) -} - -#' MCP Modeling Server -#' -#' @inheritParams mcpOutServer -mcpModelingServer <- function(id, formulasAndPriors, mcpData) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - - mcpFitList <- reactiveVal() - - observe({ - req(formulasAndPriors(), mcpData()) - # enable the 'Run Model' button - shinyjs::enable(ns("apply"), asis = TRUE) # use this instead of updateActionButton - #updateActionButton(session, "apply", disabled = FALSE) # not working with current version in docker - }) %>% bindEvent(formulasAndPriors(), mcpData()) - - observe({ - res <- runMcp( - lists = formulasAndPriors(), - data = mcpData(), - adapt = input[["adapt"]], - chains = input[["chains"]], - iter = input[["iter"]] - ) %>% - shinyTryCatch(errorTitle = "Error in fitting mcp model", warningTitle = "Warning in fitting mcp model") %>% - withProgress(message = "Fitting MCP model...", value = 0.5) - - mcpFitList(res) - }) %>% - bindEvent(input[["apply"]]) - - return(mcpFitList) - }) -} - -#' MCP Show Single Model UI -#' -#' @rdname mcpShowSingleModelServer -mcpShowSingleModelUI <- function(id) { - ns <- NS(id) - tagList( - selectInput( - ns("showModel"), - "Show MCP model", - choices = c("'Run MCP' first ..." = "") - ), - tags$br(), - fluidRow(column( - 6, - mcpOutUI( - id = ns("summary"), - title = "Model Summary", - outFUN = verbatimTextOutput, - showWidth = TRUE - ) - ), column( - 6, - mcpOutUI( - id = ns("waic"), - title = "Model WAIC", - outFUN = verbatimTextOutput - ) - )), - mcpOutUI( - id = ns("plot"), - title = "Model Plot", - outFUN = plotOutput - ) - ) -} - -#' MCP Show Single Model Server -#' -#' @inheritParams mcpOutServer -mcpShowSingleModelServer <- function(id, - mcpData, - formulasAndPriors, - mcpFitList) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - mcpModel <- reactiveVal() - - observe({ - mcpModels <- seq_along(mcpFitList()) - names(mcpModels) <- paste("Model", mcpModels) - if (length(mcpModels) > 0) - mcpSelected <- 1 - else - mcpSelected <- NULL - updateSelectInput(session, - "showModel", - choices = mcpModels, - selected = mcpSelected) - }) %>% - bindEvent(mcpFitList()) - - observe({ - req(mcpFitList(), input[["showModel"]]) - res <- mcpFitList()[[as.numeric(input[["showModel"]])]] - mcpModel(res) - }) %>% bindEvent(input[["showModel"]]) - - mcpOutServer( - id = "summary", - formulasAndPriors = formulasAndPriors, - mcpData = mcpData, - mcpFitList = mcpFitList, - mcpModel = mcpModel, - outFUN = summary, - renderFUN = renderPrint - ) - - mcpOutServer( - id = "waic", - formulasAndPriors = formulasAndPriors, - mcpData = mcpData, - mcpFitList = mcpFitList, - mcpModel = mcpModel, - outFUN = waic, - renderFUN = renderPrint - ) - - mcpOutServer( - id = "plot", - formulasAndPriors = formulasAndPriors, - mcpData = mcpData, - mcpFitList = mcpFitList, - mcpModel = mcpModel, - outFUN = plot, - renderFUN = renderPlot - ) - }) -} - -#' MCP Output UI -#' -#' @param title The title of the output -#' @param showWidth Show the width slider -#' @rdname mcpOutServer -mcpOutUI <- function(id, - title, - outFUN = verbatimTextOutput, - showWidth = FALSE) { - ns <- NS(id) - - tagList(tags$h4(title), - outFUN(ns("modelOut")) %>% withSpinner(color = "#20c997"), - if (showWidth) { - sliderInput( - ns("width"), - "Summary width", - min = 0.01, - max = 0.99, - value = 0.95, - step = 0.01, - width = "100%" - ) - }) -} - -#' MCP Output Server -#' -#' @param id The module id -#' @param formulasAndPriors The reactive formulas and priors -#' @param mcpData The reactive mcp data -#' @param mcpFitList The reactive mcp fit list -#' @param mcpModel The reactive mcp model -#' @param outFUN The output function -#' @param renderFUN The render function -mcpOutServer <- function(id, - formulasAndPriors, - mcpData, - mcpFitList, - mcpModel, - outFUN, - renderFUN = renderPrint) { - moduleServer(id, function(input, output, session) { - output$modelOut <- renderFUN({ - validate(need( - formulasAndPriors(), - "Please 'Create MCP Lists' and 'Run MCP' first ..." - )) - validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP' first ...")) - validate(need(mcpFitList(), "Please 'Run MCP' first ...")) - validate(need(mcpModel(), "Please select MCP model first ...")) - - params <- ifelse(is.null(input[["width"]]), list(), list(width = input[["width"]])) - - do.call(outFUN, c(list(mcpModel()), params)) %>% - shinyTryCatch( - errorTitle = sprintf("Error during creating '%s' output", id), - warningTitle = sprintf("Warning during creating '%s' output", id) - ) - }) - }) -} - -# 3. Comparison of Models ---- - -#' MCP Compare Models UI -#' -#' @rdname mcpCompareModelsServer -mcpCompareModelsUI <- function(id) { - ns <- NS(id) - - tagList( - selectInput(ns("method"), "Method", c("loo", "waic", "heuristic")), - tags$br(), - verbatimTextOutput(ns("compareModels")) %>% withSpinner(color = "#20c997") - ) -} - -#' MCP Compare Models Server -#' -#' @inheritParams mcpOutServer -mcpCompareModelsServer <- function(id, - formulasAndPriors, - mcpData, - mcpFitList) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - compareModels <- reactiveVal() - - output$compareModels <- renderPrint({ - validate(need( - formulasAndPriors(), - "Please 'Create MCP Lists' and 'Run MCP' first ..." - )) - validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP' first ...")) - validate(need(mcpFitList(), "Please 'Run MCP' first ...")) - - compareFUN <- switch(input[["method"]], - loo = compareWithLoo, - waic = compareWithWAIC, - heuristic = compareWithHeuristic) - - mcpFitList() %>% - compareFUN() %>% - shinyTryCatch(errorTitle = "Error in model comparison", warningTitle = "Warning in model comparison") - }) - }) -} diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index 2606089..4cd0c47 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -1,6 +1,6 @@ #' Message for no models to plot messageNoModelsToPlot <- function() { - "Select 'Model(s) / Individual(s) to display' and press 'Draw Plot' first ..." + "Select 'Model(s) / Individual(s) to display' and press 'Apply' first ..." } # We need a separate namespace for formatting inputs for the model down- and upload. @@ -21,17 +21,29 @@ timePlotFormattingUI <- function(id) { tagList( # Time Plot ---- fluidRow( - column(3, tags$h3("Time Plot")), - column(6, + column(2, tags$h3("Time Plot")), + column(3, selectizeInput(ns("plotTimeModels"), "Model(s) / Individual(s) to display", choices = c("Fit or import a model ..." = ""), multiple = TRUE, selected = "", width = "100%")), column(3, + sliderInput(ns("modCredInt"), + "Credibility interval:", + min = 0, + max = .99, + value = .8, + step = .05, + width = "100%")), + column(2, + radioButtons(ns("deriv"), + "Type", + choices = c("Absolute values" = "1", "First derivate" = "2"))), + column(2, align = "right", style = "margin-top: 1.2em;", - actionButton(ns("applyFormatToTimePlot"), "Draw Plot"), + actionButton(ns("plotTimeModels-apply"), "Apply"), plotExportButton(ns("exportCredIntTimePlot"))) ), tags$br(), @@ -45,77 +57,74 @@ timePlotFormattingUI <- function(id) { value = "formatPlotTab", tags$br(), fluidRow( - column(3, - style = "margin-top: 1.2em;", - tags$h4("Format Time Plot")), - column(6, - selectizeInput(ns("formatTimePlot"), "'Apply' formatting for Model / Individual:", - choices = defaultChoices, - width = "100%")), - column(3, - align = "right", - style = "margin-top: 1.2em;", - actionButton(ns("applyFormatToTimePlotModel"), "Apply"), - actionButton(ns("resetFormatTimePlotModel"), "Reset Format")) - ), - tags$br(), - fluidRow( - column(3, - tags$h4("Data"), - radioButtons(ns("deriv"), - "Type", - choices = c("Absolute values" = "1", "First derivate" = "2")), - sliderInput(ns("modCredInt"), - "Credibility interval:", - min = 0, - max = .99, - value = .8, - step = .05, - width = "100%"), - tags$br(), - sliderInput(ns("alphaU"), - "Transparency of uncertainty region", - min = 0, - max = 1, - value = 0.1, - step = 0.05), - sliderInput(ns("alphaL"), - "Transparency of points / lines", - min = 0, - max = 1, - value = 0.9, - step = 0.05), - tags$br(), - plotLegendUI(ns("legend")) - ), - column(3, - shinyTools::plotTitlesUI( - id = ns("plotLabels"), - title = "Text", - type = "ggplot", - initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], - xAxisText = config()[["defaultIntervalTimePlotText"]]) + column(4, + tags$h4("Plot"), + tabsetPanel( + id = ns("tabs_text_legend"), + selected = "Data", + tabPanel("Data", + sliderInput(ns("alphaU"), + "Transparency of uncertainty region", + min = 0, + max = 1, + value = 0.1, + step = 0.05), + sliderInput(ns("alphaL"), + "Transparency of points / lines", + min = 0, + max = 1, + value = 0.9, + step = 0.05) + ), + tabPanel("Axis", + shinyTools::plotRangesUI(id = ns("plotRanges"), title = NULL), + conditionalPanel( + ns = ns, + condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'xAxis'", + checkboxInput(inputId = ns("extendLabels"), + label = "Extend x-axis labels to full range", + value = FALSE) + ), + tags$br(), tags$br(), + selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", + choices = c("Choose one Model / Individual ..." = "")), + helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis."), + actionButton(ns("axis-apply"), "Apply") + ), + tabPanel("Text", + shinyTools::plotTitlesUI( + id = ns("plotLabels"), + title = NULL, + type = "ggplot", + initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], + xAxisText = config()[["defaultIntervalTimePlotText"]]) + ), + actionButton(ns("plotLabels-apply"), "Apply") + ), + tabPanel("Legend", + shinyTools::plotLegendUI(ns("legend")) + ) ) ), - column(3, - shinyTools::plotRangesUI(id = ns("plotRanges"), title = "Axis"), - conditionalPanel( - ns = ns, - condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'xAxis'", - checkboxInput(inputId = ns("extendLabels"), - label = "Extend x-axis labels to full range", - value = FALSE) - ), - tags$br(), tags$br(), - selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", - choices = c("Choose one Model / Individual ..." = "")), - helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis.") + column(4, + tags$h4("Model / Individual"), + tabsetPanel( + id = ns("tabs_models"), + selected = "Points & Lines", + tabPanel("Points & Lines", + selectizeInput(ns("formatTimePlot"), "Select model / individual:", + choices = defaultChoices, + width = "100%"), + shinyTools::plotPointsUI(id = ns("pointStyle"), + title = NULL, + initStyle = config()[["defaultPointStyle"]]), + actionButton(ns("pointStyle-apply"), "Apply"), + actionButton(ns("pointStyle-reset"), "Reset Models") + ) + ) ), - column(3, - shinyTools::plotPointsUI(id = ns("pointStyle"), - title = "Points / Lines", - initStyle = config()[["defaultPointStyle"]]) - ) + # custom points ---- + column(4, shinyTools::customPointsUI(ns("customPoints"))) ) ), tabPanel( @@ -130,7 +139,7 @@ timePlotFormattingUI <- function(id) { # Break Point Detection ---- "Break point detection", value = "breakPointTab", - breakPointDetectionUI(ns("breakPointDetection")) + changePointsUI(ns("changePoints")) ) ) , @@ -151,12 +160,10 @@ timePlotFormattingServer <- function(id, savedModels) { function(input, output, session) { ns <- session$ns - formattedPlot <- reactiveVal() - plotTexts <- shinyTools::plotTitlesServer( "plotLabels", type = "ggplot", - availableElements = c("title", "axis", "yaxis2", "legend"), + availableElements = c("title", "axis", "yaxis2"), showParseButton = FALSE, initText = getDefaultTextFormat() ) @@ -176,16 +183,21 @@ timePlotFormattingServer <- function(id, savedModels) { hideInput = c("hide", "alpha", "colorBg") ) - legend <- shinyTools::plotLegendServer("legend") - - pointStyleList <- reactiveValues() + pointStyleList <- reactiveVal(list()) # empty list, no models yet + plotAxisStyleList <- reactiveVal(list(xAxis = config()[["plotRange"]], + yAxis = config()[["plotRange"]], + yAxis2 = config()[["plotRange"]], + secAxisModel = "", + extendLabels = FALSE)) + plotTextStyleList <- reactiveVal(getDefaultTextFormat()) observe({ req(length(savedModels()) > 0) modelNames <- names(savedModels()) - pointStyleList <- pointStyleList %>% + newList <- pointStyleList() %>% getDefaultPointFormatForModels(modelNames = modelNames) + pointStyleList(newList) updateSelectizeInput(session, "plotTimeModels", choices = modelNames, @@ -218,13 +230,6 @@ timePlotFormattingServer <- function(id, savedModels) { }) %>% bindEvent(input[["plotTimeModels"]], ignoreNULL = FALSE, ignoreInit = TRUE) - observe({ - req(input[["formatTimePlot"]]) - # observe point style - pointStyleList[[input[["formatTimePlot"]]]] <- pointStyle[["dataPoints"]] - }) %>% - bindEvent(input[["applyFormatToTimePlotModel"]]) - allFits <- reactive({ getEntry(savedModels(), "fit") }) @@ -238,14 +243,21 @@ timePlotFormattingServer <- function(id, savedModels) { removeEmptyModels() }) - extractedPlotDataDF <- reactive({ - extractedPlotDataList() %>% + extractedPlotDataDF <- reactiveVal() + observe({ + req(savedModels(), input[["plotTimeModels"]]) + logDebug("%s: Entering get extractedPlotDataDF()", id) + + df <- extractedPlotDataList() %>% extractPlotDataDF(models = input[["plotTimeModels"]], credInt = input$modCredInt) %>% shinyTryCatch(errorTitle = "'Credibility intervals over time': Error when extracting data", warningTitle = "'Credibility intervals over time': Warning when extracting data", alertStyle = "shinyalert") - }) + + extractedPlotDataDF(df) + }) %>% + bindEvent(input[["plotTimeModels-apply"]]) # render plot data table ---- output$plotData <- renderTable({ @@ -256,98 +268,166 @@ timePlotFormattingServer <- function(id, savedModels) { }) # export plot data ---- - plotDataExport <- reactiveVal() + dataExportServer("exportCredIntTimeData", filename = "timePlotData", reactive(function() { + if (length(input[["plotTimeModels"]]) == 0 || + any(input[["plotTimeModels"]] == "")) + return(NULL) + + extractedPlotDataDF() + })) + + legend <- shinyTools::plotLegendServer( + "legend", + legend_title = reactive({"individual"}), + legend_labels = reactive({levels(na.omit(extractedPlotDataDF())[["individual"]])})) + + # custom points ---- + custom_points <- reactiveVal(list()) + customPointsServer("customPoints", plot_type = "ggplot", custom_points = custom_points) + + # buttons: input[["plotTimeModels"]] button ----- + # disable if nothing is selected in + buttons <- c("plotTimeModels-apply", "axis-apply", "plotLabels-apply") + lapply(buttons, function(btn) { + observe({ + if (length(input[["plotTimeModels"]]) == 0 || + any(input[["plotTimeModels"]] == "")) { + logDebug("%s: Disable button '%s'", id, btn) + shinyjs::disable(ns(btn), asis = TRUE) + } else { + logDebug("%s: Enable button '%s'", id, btn) + shinyjs::enable(ns(btn), asis = TRUE) + } + }) + }) observe({ - plotDataExport(extractedPlotDataDF()) + logDebug("%s: Apply new point layout", id) + newList <- plotAxisStyleList() + for (name in names(plotRanges)) { + newList[[name]] <- plotRanges[[name]] + } + newList[["extendLabels"]] <- input[["extendLabels"]] + newList[["secAxisModel"]] <- input[["secAxisModel"]] + + plotAxisStyleList(newList) }) %>% - bindEvent(input[["plotTimeModels"]]) + bindEvent(input[["axis-apply"]]) - dataExportServer("exportCredIntTimeData", - reactive(function() {plotDataExport()})) + observe({ + logDebug("%s: Apply new text layout", id) + newList <- plotTextStyleList() + for (name in names(plotTexts)) { + newList[[name]] <- plotTexts[[name]] + } + + plotTextStyleList(newList) + }) %>% + bindEvent(input[["plotLabels-apply"]]) - newPlot <- reactive({ - logDebug("%s: Entering: reactive 'newPlot'", id) + # button: input[["formatTimePlot"]] ---- + # disable if nothing is selected in + observe({ + if (length(input[["formatTimePlot"]]) == 0 || + any(input[["formatTimePlot"]] == "")) { + logDebug("%s: Disable button 'pointStyle-apply'", id) + shinyjs::disable(ns("pointStyle-apply"), asis = TRUE) + } else { + logDebug("%s: Enable button 'pointStyle-apply'", id) + shinyjs::enable(ns("pointStyle-apply"), asis = TRUE) + } + }) + + observe({ + logDebug("%s: Apply new point layout", id) - # setup base plot + # update point style list + newList <- pointStyleList() + newList[[input[["formatTimePlot"]]]] <- pointStyle[["dataPoints"]] + pointStyleList(newList) + }) %>% + bindEvent(input[["pointStyle-apply"]]) + + observe({ + req(savedModels(), input[["plotTimeModels"]]) + logDebug("%s: Entering reset pointStyleList()", id) + defaultStyle <- pointStyleList() %>% + getDefaultPointFormatForModels(modelNames = names(savedModels()), isFullReset = TRUE) + newList <- pointStyleList() + newList[[input[["formatTimePlot"]]]] <- defaultStyle[[input[["formatTimePlot"]]]] + pointStyleList(newList) + }) %>% + bindEvent(input[["pointStyle-reset"]]) + + # render plot ---- + plotToExport <- reactiveVal(NULL) + output$plotTime <- renderPlot({ + validate(need(extractedPlotDataDF(), messageNoModelsToPlot())) + + logDebug("%s: draw basePlot", id) p <- extractedPlotDataDF() %>% na.omit() %>% - rescaleSecondAxisData(individual = input[["secAxisModel"]], - plotRanges = plotRanges) %>% - basePlotTime(xLim = getUserLimits(plotRanges = plotRanges[["xAxis"]]), - yLim = getUserLimits(plotRanges = plotRanges[["yAxis"]])) %>% - setDefaultTitles(prop = input$modCredInt) %>% - shinyTools::formatTitlesOfGGplot(text = plotTexts) + rescaleSecondAxisData(individual = plotAxisStyleList()[["secAxisModel"]], + plotRanges = plotAxisStyleList()) %>% + basePlotTime(xLim = getUserLimits(plotRanges = plotAxisStyleList()[["xAxis"]]), + yLim = getUserLimits(plotRanges = plotAxisStyleList()[["yAxis"]])) %>% + setDefaultTitles(prop = isolate(input[["modCredInt"]])) %>% + shinyTools::formatTitlesOfGGplot(text = plotTextStyleList()) %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") # specify x-axis labels from x data of all models allXAxisData <- extractedPlotDataList() %>% extractAllXAxisData() %>% - extendXAxis(xLabelLim = getUserLimits(plotRanges = plotRanges[["xAxis"]]), - extendLabels = input$extendLabels) + extendXAxis(xLabelLim = getUserLimits(plotRanges = plotAxisStyleList()[["xAxis"]]), + extendLabels = plotAxisStyleList()[["extendLabels"]]) - p %>% + logDebug("%s: draw lines", id) + p <- p %>% + drawLinesAndRibbon( + pointStyleList = pointStyleList(), + alphaL = input[["alphaL"]], + alphaU = input[["alphaU"]], + legend = reactiveValuesToList(legend) + ) %>% shinyTools::formatScalesOfGGplot( - ranges = plotRanges, + ranges = plotAxisStyleList(), xLabels = list( # input$deriv already included within extractedPlotDataList() breaks = getBreaks(time = allXAxisData$time, deriv = "1"), labels = getLabel(xAxisData = allXAxisData, deriv = "1") ), - ySecAxisTitle = getSecAxisTitle(modelName = input[["secAxisModel"]], - customTitle = plotTexts[["yAxisTitle2"]]) + ySecAxisTitle = getSecAxisTitle(modelName = plotAxisStyleList()[["secAxisModel"]], + customTitle = plotTextStyleList()[["yAxisTitle2"]]) ) %>% - drawLinesAndRibbon( - pointStyleList = pointStyleList, - alphaL = input[["alphaL"]], - alphaU = input[["alphaU"]], - # UPDATE LEGEND HERE <- ------- - legendName = plotTexts[["legendTitle"]][["text"]] - ) %>% - shinyTools::formatLegendOfGGplot(legend = legend) - }) - - observe({ - req(savedModels(), input[["plotTimeModels"]]) - logDebug("%s: Entering get formattedPlot()", id) - - p <- newPlot() %>% + shinyTools::addCustomPointsToGGplot(custom_points = custom_points()) %>% shinyTools::shinyTryCatch(errorTitle = "Plotting failed") - formattedPlot(p) - }) %>% - bindEvent(list(input[["applyFormatToTimePlot"]], - input[["applyFormatToTimePlotModel"]])) - - observe({ - req(savedModels(), input[["plotTimeModels"]]) - logDebug("%s: Entering reset formattedPlot()", id) - - modelNames <- names(savedModels()) - defaultStyle <- pointStyleList %>% - reactiveValuesToList() %>% - getDefaultPointFormatForModels(modelNames = modelNames) - pointStyleList[[input[["formatTimePlot"]]]] <- defaultStyle[[input[["formatTimePlot"]]]] - p <- newPlot() %>% - shinyTools::shinyTryCatch(errorTitle = "Plotting failed") - formattedPlot(p) - }) %>% - bindEvent(input[["resetFormatTimePlotModel"]]) - - # render plot ---- - output$plotTime <- renderPlot({ - validate(need(formattedPlot(), messageNoModelsToPlot())) - formattedPlot() + plotToExport(p) + p }) # export plot ---- plotExportServer("exportCredIntTimePlot", - plotFun = reactive(function() formattedPlot()), + plotFun = reactive(function() plotToExport()), filename = sprintf("%s_Credibility_Intervals_Over_Time", gsub("-", "", Sys.Date())) ) # Break point detection ---- - breakPointDetectionServer(id = "breakPointDetection", plotData = extractedPlotDataDF) + changePointData <- reactiveValues() + observe({ + changePointData$mainData <- extractedPlotDataDF() + }) %>% + bindEvent(extractedPlotDataDF()) + + changePointsServer( + "changePoints", + file_data = changePointData, + mcp_columns = c(x = "time", y = "median") + ) + # note: restoring a whole session as in ChangeR will not be possible (much too + # complex) since inputs are send much earlier than all reactive objects are updated. + # As a result the inputs cannot be set correctly and plots will remain empty. }) } diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index d931ea1..71a6dbc 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -11,34 +11,29 @@ #' @import rstantools #' @import shiny #' @import shinythemes -#' @importFrom colourpicker colourInput +#' @importFrom ChangeR changePointsServer changePointsUI #' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer #' importDataUI importDataServer importUI importServer importOptions renameExistingNames -#' @importFrom dplyr arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n select -#' slice ungroup +#' @importFrom dplyr %>% arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n +#' select slice ungroup #' @importFrom ggplot2 aes coord_cartesian element_line element_text #' geom_line geom_point geom_ribbon geom_vline ggplot ggplot_build ggtitle labs #' scale_colour_manual scale_fill_manual scale_shape_manual scale_size_manual #' scale_x_continuous scale_y_continuous sec_axis theme -#' @importFrom htmltools save_html -#' @importFrom jsonlite toJSON -#' @importFrom loo loo loo_compare waic -#' @importFrom magrittr %>% -#' @importFrom mcp mcp -#' @importFrom openxlsx write.xlsx #' @importFrom parallel detectCores #' @importFrom rlang .data #' @importFrom rstan sampling extract #' @importFrom shinycssloaders withSpinner #' @importFrom shinyjs alert enable runjs -#' @importFrom shinyTools calculateRescalingFactors dataExportButton dataExportServer extractTitle -#' formatLegendOfGGplot formatPointsOfGGplot formatScalesOfGGplot formatTitlesOfGGplot +#' @importFrom shinyTools addCustomPointsToGGplot calculateRescalingFactors +#' customPointsServer customPointsUI dataExportButton dataExportServer +#' extractTitle formatPointsOfGGplot formatScalesOfGGplot formatTitlesOfGGplot #' plotExportButton plotExportServer plotLegendServer plotLegendUI plotPointsServer plotPointsUI -#' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI +#' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI setLegendThemeOfGGplot #' shinyTryCatch #' @importFrom shinyWidgets pickerInput updatePickerInput -#' @importFrom stats approx as.formula dnorm lm median na.omit quantile sd -#' @importFrom utils read.csv write.csv write.table combn +#' @importFrom stats approx dnorm lm median na.omit quantile sd +#' @importFrom utils write.csv combn #' @importFrom yaml read_yaml #' @references #' Stan Development Team (NA). RStan: the R interface to Stan. R package version NA. http://mc-stan.org diff --git a/R/exportData.R b/R/exportData.R deleted file mode 100644 index f92e8f7..0000000 --- a/R/exportData.R +++ /dev/null @@ -1,38 +0,0 @@ -#' Filename of Export -#' -#' @param fileending character csv or xlsx -#' @export -exportFilename <- function(fileending){ - paste("isotopeData", fileending, sep = ".") -} - -#' Export to csv -#' -#' @param file filename -#' @param dat data.frame -#' @param colseparator column seperator -#' @param decseparator decimal seperator -#' @export -exportCSV <- function(file, dat, colseparator, decseparator){ - write.table(x = dat, file = file, sep = colseparator, - dec = decseparator, row.names = FALSE) -} - -#' Export to xlsx -#' -#' @param file filename -#' @param dat data.frame -#' @export -exportXLSX <- function(file, dat){ - write.xlsx(dat, file) -} - -#' Export to json -#' -#' @param file filename -#' @param dat data.frame -#' @export -exportJSON <- function(file, dat){ - json <- toJSON(dat) - write(json, file) -} \ No newline at end of file diff --git a/R/help.R b/R/help.R deleted file mode 100644 index e0a50c5..0000000 --- a/R/help.R +++ /dev/null @@ -1,10 +0,0 @@ -#' Get Text for Help Panel in Shiny App -#' -#' @param id id of selected tab -#' -#' @export -getHelp <- function(id) { - tagList( - tags$p("This is the help") - ) -} diff --git a/R/plots.R b/R/plots.R index f1dc533..fd9001f 100644 --- a/R/plots.R +++ b/R/plots.R @@ -90,10 +90,10 @@ basePlotTime <- function(df, -drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName = "individual") { - if (nrow(plot$data) == 0) return(plot) +drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legend = NULL) { + if (is.null(plot) || nrow(plot$data) == 0) return(plot) - # draw lines "upper", "median", "lower" + # draw lines "upper", "median", "lower" for each "individual" if (nrow(plot$data) > 1) { plot <- plot + geom_line(aes(y = .data[["median"]], colour = .data[["individual"]]), @@ -112,7 +112,7 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName size = 0.05, alpha = alphaL) } - # draw ribbon + # draw ribbon for each "individual" if (nrow(plot$data) > 1) { plot <- plot + geom_ribbon(aes(ymin = .data[["lower"]], ymax = .data[["upper"]], @@ -120,7 +120,7 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName linetype = 2, alpha = alphaU) } - # draw points "median" + # draw points "median" for each "individual" plot <- plot + geom_point(aes(y = .data[["median"]], colour = .data[["individual"]], @@ -129,6 +129,8 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName fill = .data[["individual"]]), alpha = alphaL) + if (length(pointStyleList) == 0) return(plot) + # set scales for each "individual" with default legend name lineColors <- getStyleForIndividuals(pointStyleList, input = "color") fillColors <- getStyleForIndividuals(pointStyleList, input = "color") @@ -136,13 +138,26 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName pointSize <- getStyleForIndividuals(pointStyleList, input = "size") # default legend name for empty input - if (legendName == "") legendName <- "individual" + if (!is.null(legend)) { + legendName <- extractTitle(legend$layout$title[[1]], default = "individual") + legendLabels <- sapply(names(legend$layout$labels), function(name) { + extractTitle(legend$layout$labels[[name]], default = name) + }) + + plot <- plot %>% setLegendThemeOfGGplot(legend = legend) + } else { + legendName <- "individual" + legendLabels <- names(lineColors) + names(legendLabels) <- names(lineColors) + } - plot + - scale_colour_manual(name = legendName, values = lineColors) + # former colorL - scale_fill_manual(name = legendName, values = fillColors) + # former colorU - scale_shape_manual(name = legendName, values = pointShapes) + - scale_size_manual(name = legendName, values = pointSize) + plot <- plot + + scale_colour_manual(name = legendName, labels = legendLabels, values = lineColors) + # former colorL + scale_fill_manual(name = legendName, labels = legendLabels, values = fillColors) + # former colorU + scale_shape_manual(name = legendName, labels = legendLabels, values = pointShapes) + + scale_size_manual(name = legendName, labels = legendLabels, values = pointSize) + + plot } setDefaultTitles <- function(plot, prop, xAxisLabel = "Time", yAxisLabel = "Estimate") { diff --git a/inst/app/data/example.csv b/inst/app/data/example.csv new file mode 100644 index 0000000..3aedfcb --- /dev/null +++ b/inst/app/data/example.csv @@ -0,0 +1,12 @@ +"";"individual";"intStart";"intEnd";"bone1";"bone2";"tooth1";"tooth2" +"1";1;0;1;100;100;0;0 +"2";1;1;2;50;10;100;0 +"3";1;2;3;20;5;0;100 +"4";11;4;5;5;1;0;0 +"5";11;5;6;2;1;0;0 +"6";2;0;1;100;90;0;NA +"7";2;1;2;80;40;80;NA +"8";2;2;3;30;5;20;NA +"9";2;3;4;8;1;0;NA +"10";2;4;5;15;12;0;NA +"11";2;5;6;4;1;0;NA diff --git a/inst/app/data/example_breakPoints.csv b/inst/app/data/example_breakPoints.csv deleted file mode 100644 index 4760dbd..0000000 --- a/inst/app/data/example_breakPoints.csv +++ /dev/null @@ -1,15 +0,0 @@ -x,y -0.25,12.53 -0.75,12.03 -1.25,11.54 -1.75,11.5 -2.25,11.5 -2.75,11.53 -3.25,11.67 -3.75,11.75 -4.75,11.99 -5.25,12.01 -6.75,11.68 -7.25,11.66 -7.75,11.66 -8.25,11.68 diff --git a/inst/app/server.R b/inst/app/server.R index 228e38e..a467a45 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -487,52 +487,35 @@ shinyServer(function(input, output, session) { bindEvent(uploadedValues()) ## Export Summary ---- - observeEvent(input$exportSummary, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteSummary", "Export") - )) - }) - - output$exportExecuteSummary <- downloadHandler( - filename = function(){ - exportFilename(input$exportType) - }, - content = function(file){ - req(fit()) + shinyTools::dataExportServer("exportSummary", filename = "isotopeData", dataFun = reactive({ + function() { + if (is.null(fit())) + return(NULL) + exportData <- as.data.frame(extract(fit())$interval) namesStan <- names(fit()) intervalNamesStan <- namesStan[grepl(pattern = "interval", namesStan)] colnames(exportData) <- intervalNamesStan - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) + + exportData } - ) + })) + + # custom points ---- + custom_points <- reactiveVal(list()) + shinyTools::customPointsServer("customPoints", plot_type = "ggplot", custom_points = custom_points) + # render "Credibility Intervals" plot ---- + plotToExport <- reactiveVal(NULL) output$plot <- renderPlot({ req(fit()) #OsteoBioR::plot(fit(), prop = input$modCredInt) - plot(fit(), prop = input$modCredInt) - }) + p <- plot(fit(), prop = input$modCredInt) %>% + shinyTools::addCustomPointsToGGplot(custom_points = custom_points()) %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") + plotToExport(p) + p + }) # create plotTime ---- timePlotFormattingServer(id = "timePlotFormat", savedModels = savedModels) @@ -607,145 +590,41 @@ shinyServer(function(input, output, session) { # ) # }) + shinyTools::dataExportServer("exportTimePointEst", + filename = "timePointEstimates", + dataFun = reactive({ + function() { + if (length(input$savedModelsTime) == 0 || + any(input$savedModelsTime == "") || + length(input$estSpecTimePoint) == 0 || + input$estSpecTimePoint == 0 || + is.character(estimates())) + return(NULL) + + estimates() + } + })) + + shinyTools::dataExportServer("exportCredIntDat", + filename = "credibilityIntervals", + dataFun = reactive({ + function() { + if (is.null(fit())) + return(NULL) + + as.data.frame(fit()) + } + })) - observeEvent(input$exportTimePointEst, { - req(estimates()) - req(!is.character(estimates())) - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteTimePointEst", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExecuteTimePointEst <- downloadHandler( - filename = function(){ - paste("timePointEstimates", input$exportType, sep = ".") - }, - content = function(file){ - exportData <- estimates() - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) - - observeEvent(input$exportCredIntDat, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteCredIntDat", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExecuteCredIntDat <- downloadHandler( - filename = function(){ - paste("credibilityIntervals", input$exportType, sep = ".") - }, - content = function(file){ - req(fit()) - exportData <- as.data.frame(fit()) - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) - # Downloads Plots ------------------------------------------------- - - observeEvent(input$exportCredIntPlot, { - - plotOutputElement <- renderPlot({ - req(fit()) - plot(fit(), prop = input$modCredInt) - #OsteoBioR::plot(fit(), prop = input$modCredInt) - }) - exportTypeChoices <- c("png", "pdf", "svg", "tiff") - - showModal(modalDialog( - title = "Export Graphic", - footer = modalButton("OK"), - plotOutputElement, - selectInput( - "exportType", "Filetype", - choices = exportTypeChoices - ), - numericInput("width", "Width (px)", value = 1280), - numericInput("height", "Height (px)", value = 800), - downloadButton("exportExecute", "Export"), - easyClose = TRUE - )) - - output$exportExecute <- downloadHandler( - filename = function(){ - paste0(gsub("-", "", Sys.Date()), "_", "Credibility_Intervals", ".", input$exportType) - }, - content = function(file){ - switch( - input$exportType, - png = png(file, width = input$width, height = input$height), - pdf = pdf(file, width = input$width / 72, height = input$height / 72), - tiff = tiff(file, width = input$width, height = input$height), - svg = svg(file, width = input$width / 72, height = input$height / 72) - ) - print({ - req(fit()) - #OsteoBioR::plot(fit(), prop = input$modCredInt) - plot(fit(), prop = input$modCredInt) - }) - - dev.off() - } - ) - }) + shinyTools::plotExportServer("exportCredIntPlot", + plotFun = reactive({ + function() { + plotToExport() + } + }), + plotType = "none", #"ggplot", #<- fix issue with labels first + filename = sprintf("%s_Credibility_Intervals", gsub("-", "", Sys.Date()))) # RESIDING TIME ------------------------------------------ datStayTime <- reactiveValues() @@ -795,51 +674,19 @@ shinyServer(function(input, output, session) { }) output$estimatedStayTimes <- renderPrint({ estimatedStayTimes() }) - observeEvent(input$exportStayTimeDat, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteStayTimeDat", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExecuteStayTimeDat <- downloadHandler( - filename = function(){ - paste("stayTimeLengths", input$exportType, sep = ".") - }, - content = function(file){ - resTime <- estimatedStayTimes() - exportData <- as.data.frame(resTime$stayTimes) - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) - + shinyTools::dataExportServer("exportStayTimeDat", + filename = "stayTimeLengths", + dataFun = reactive({ + function() { + if (length(fit()) == 0 || + is.null(input$stayingTime) || + input$stayingTime == 0 || length(estimatedStayTimes()) == 0) + return(NULL) + + resTime <- estimatedStayTimes() + as.data.frame(resTime$stayTimes) + } + })) # COMPUTE ISOTOPIC VALUES ------------------------------ datIso <- reactiveValues() @@ -908,49 +755,19 @@ shinyServer(function(input, output, session) { output$isotopicValues <- renderTable({ isotopicValues() }, rownames = TRUE) - observeEvent(input$exportResultsDat, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExportResultsDat", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExportResultsDat <- downloadHandler( - filename = function(){ - paste("isotopicValues", input$exportType, sep = ".") - }, - content = function(file){ - exportData <- isotopicValues() - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) + shinyTools::dataExportServer("exportResultsDat", + filename = "isotopicValues", + dataFun = reactive({ + function() { + if (length(input$historicData) == 0 || + any(input$historicData == "") || + length(input$calcIsotopicValues) == 0 || + input$calcIsotopicValues == 0) + return(NULL) + + isotopicValues() + } + })) observeEvent(input$getHelp, { showModal(modalDialog( diff --git a/inst/app/ui.R b/inst/app/ui.R index 1cf798e..975998c 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -6,6 +6,7 @@ library(OsteoBioR) library(shinyMatrix) library(dplyr) library(shinycssloaders) +library(shinyTools) library(ggplot2) library(rstan) @@ -171,15 +172,18 @@ tagList( value = "summaryTab", verbatimTextOutput("summary") %>% withSpinner(color = "#20c997"), - actionButton("exportSummary", "Export Interval Data") + shinyTools::dataExportButton("exportSummary", label = "Export Interval Data") ), tabPanel( "Credibility Intervals", value = "credibilityIntervalsTab", plotOutput("plot") %>% withSpinner(color = "#20c997"), - actionButton("exportCredIntPlot", "Export Plot"), - actionButton("exportCredIntDat", "Export Data") + fluidRow(column(4, shinyTools::customPointsUI("customPoints")), + column(8, align = "right", + shinyTools::plotExportButton("exportCredIntPlot", label = "Export Plot"), + shinyTools::dataExportButton("exportCredIntDat", label = "Export Data") + )), ), tabPanel( "Credibility intervals over time", @@ -252,7 +256,7 @@ tagList( ), column(4, tags$br(), - actionButton("exportTimePointEst", "Export Time Point Estimates") + shinyTools::dataExportButton("exportTimePointEst", label = "Export Time Point Estimates") ) ), tags$br(), @@ -335,7 +339,7 @@ tagList( HTML("