diff --git a/CITATION.cff b/CITATION.cff index 9d517f96..86c9ebe0 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,7 +8,7 @@ message: 'To cite package "FreesearchR" in publications use:' type: software license: AGPL-3.0-or-later title: 'FreesearchR: Easy data analysis for clinicians' -version: 26.6.1 +version: 26.4.2 doi: 10.5281/zenodo.14527429 identifiers: - type: url diff --git a/DESCRIPTION b/DESCRIPTION index cc854ec0..69564bc2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: FreesearchR Title: Easy data analysis for clinicians -Version: 26.6.1 +Version: 26.4.2 Authors@R: c( person("Andreas Gammelgaard", "Damsbo",email="agdamsbo@clin.au.dk", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-7559-1154")), @@ -118,7 +118,6 @@ Collate: 'launch_FreesearchR.R' 'missings-module.R' 'plot-download-module.R' - 'plot-helpers.R' 'plot_bar.R' 'plot_box.R' 'plot_euler.R' diff --git a/NAMESPACE b/NAMESPACE index 947b97e8..9e036c3f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,6 @@ export(append_column) export(append_list) export(apply_labels) export(argsstring2list) -export(available_plots) export(baseline_table) export(class_icons) export(clean_common_axis) @@ -65,7 +64,6 @@ export(format_writer) export(generate_colors) export(get_data_packages) export(get_fun_options) -export(get_input_params) export(get_label) export(get_list_elements) export(get_plot_options) diff --git a/NEWS.md b/NEWS.md index ce86f7a7..785ee46a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,3 @@ -# FreesearchR 26.6.1 - -*NEW* The visuals module has been restructured to allow for more advanced inputs, which will be added in the future. Basically a more future proof design allowing for more adjustments, while striving to keep the simplicity. Have fun! - # FreesearchR 26.4.2 Bug fixes and revised color choices. diff --git a/R/app_version.R b/R/app_version.R index bce90462..2cbd2cc4 100644 --- a/R/app_version.R +++ b/R/app_version.R @@ -1 +1 @@ -app_version <- function()'26.6.1' +app_version <- function()'26.4.2' diff --git a/R/data_plots.R b/R/data_plots.R index b9e84c85..a01403df 100644 --- a/R/data_plots.R +++ b/R/data_plots.R @@ -14,23 +14,12 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { list( bslib::layout_sidebar( sidebar = bslib::sidebar( - shiny::actionButton( - inputId = ns("act_plot"), - label = i18n$t("Plot"), - width = "100%", - icon = phosphoricons::ph("paint-brush", weight = "bold"), - # icon = shiny::icon("palette"), - disabled = FALSE - ), - shiny::helpText( - i18n$t('Adjust plot input and settings below, then press "Plot".') - ), bslib::accordion( id = "acc_plot", multiple = FALSE, bslib::accordion_panel( value = "acc_pan_plot", - title = i18n$t("Define plot"), + title = "Create plot", icon = phosphoricons::ph("chart-line"), # icon = bsicons::bs_icon("graph-up"), shiny::uiOutput(outputId = ns("primary")), @@ -41,16 +30,19 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { ), shiny::tags$br(), shiny::uiOutput(outputId = ns("type")), - shiny::h5(i18n$t("Other variables")), shiny::uiOutput(outputId = ns("secondary")), - shiny::uiOutput(outputId = ns("tertiary")) - ), - bslib::accordion_panel( - value = "acc_pan_params", - title = i18n$t("Settings"), - icon = phosphoricons::ph("gear"), + shiny::uiOutput(outputId = ns("tertiary")), shiny::uiOutput(outputId = ns("color_palette")), - shiny::uiOutput(outputId = ns("basic_parameters")), + shiny::br(), + shiny::actionButton( + inputId = ns("act_plot"), + label = i18n$t("Plot"), + width = "100%", + icon = phosphoricons::ph("paint-brush",weight = "bold"), + # icon = shiny::icon("palette"), + disabled = FALSE + ), + shiny::helpText(i18n$t('Adjust settings, then press "Plot".')) ), bslib::accordion_panel( value = "acc_pan_download", @@ -103,14 +95,14 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { shiny::p( "We have collected a few notes on visualising data and details on the options included in FreesearchR:", shiny::tags$a( - href = "https://freesearchr.github.io/FreesearchR-knowledge/app/visuals.html", + href = "https://agdamsbo.github.io/FreesearchR/articles/visuals.html", "View notes in new tab", target = "_blank", rel = "noopener noreferrer" ) ) ), - shiny::plotOutput(ns("plot"), height = "65vh"), + shiny::plotOutput(ns("plot"), height = "70vh"), shiny::tags$br(), shiny::tags$br(), shiny::htmlOutput(outputId = ns("code_plot")) @@ -127,7 +119,10 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { #' @name data-plots #' @returns shiny server module #' @export -data_visuals_server <- function(id, data, palettes = color_choices(), ...) { +data_visuals_server <- function(id, + data, + palettes, + ...) { shiny::moduleServer( id = id, module = function(input, output, session) { @@ -179,99 +174,69 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { plot_data <- data()[input$primary] } - plots <- possible_plots(data = plot_data, source_list = available_plots()) + plots <- possible_plots(data = plot_data) - plots_named <- get_input_params(plots) |> + plots_named <- get_plot_options(plots) |> lapply(\(.x) { stats::setNames(.x$descr, .x$note) }) - # plots_named <- get_plot_options(plots) |> - # lapply(\(.x) { - # stats::setNames(.x$descr, .x$note) - # }) - vectorSelectInput( inputId = ns("type"), selected = NULL, - label = shiny::h5(i18n$t("Plot type")), + label = shiny::h4(i18n$t("Plot type")), choices = Reduce(c, plots_named), multiple = FALSE ) }) rv$plot.params <- shiny::reactive({ - get_input_params(input$type) |> purrr::pluck(1) - # get_plot_options(input$type) |> purrr::pluck(1) + get_plot_options(input$type) |> purrr::pluck(1) }) - - ### Include two additional variable inputs output$secondary <- shiny::renderUI({ shiny::req(input$type) - # Get the plot function name - base_params <- rv$plot.params()[["base"]] + cols <- c(rv$plot.params()[["secondary.extra"]], all_but(colnames( + subset_types(data(), rv$plot.params()[["secondary.type"]]) + ), input$primary)) - filtered_params <- base_params[sapply(base_params, function(params) { - params$id %in% "secondary" - })][[1]] - - filtered_params$exclude <- input$primary - - create_input_element( - input_id = "secondary", - ns = ns, - params = append_list(data(), filtered_params, "data") + columnSelectInput( + inputId = ns("secondary"), + data = data, + selected = cols[1], + placeholder = i18n$t("Please select"), + label = if (isTRUE(rv$plot.params()[["secondary.multi"]])) + i18n$t("Additional variables") + else + i18n$t("Secondary variable"), + multiple = rv$plot.params()[["secondary.multi"]], + maxItems = rv$plot.params()[["secondary.max"]], + col_subset = cols, + none_label = i18n$t("No variable") ) - }) output$tertiary <- shiny::renderUI({ shiny::req(input$type) - # Get the plot function name - base_params <- rv$plot.params()[["base"]] - - filtered_params <- base_params[sapply(base_params, function(params) { - params$id %in% "tertiary" - })][[1]] - - filtered_params$exclude <- c(input$primary, input$secondary) - - create_input_element( - input_id = "tertiary", - ns = ns, - params = append_list(data(), filtered_params, "data") + columnSelectInput( + inputId = ns("tertiary"), + data = data, + placeholder = i18n$t("Please select"), + label = i18n$t("Grouping variable"), + multiple = FALSE, + col_subset = c( + "none", + all_but( + colnames(subset_types(data(), rv$plot.params()[["tertiary.type"]])), + input$primary, + input$secondary + ) + ), + none_label = i18n$t("No stratification") ) }) - - ### Generating additional parameter inputs if any specified - output$basic_parameters <- renderUI({ - req(input$type, rv$plot.params) - - # Get the plot function name - base_params <- rv$plot.params()[["base"]] - - filtered_params <- base_params[sapply(base_params, function(params) { - !params$id %in% c("secondary", "tertiary") - })] - - - # Create UI elements for base parameters - base_inputs <- lapply(filtered_params, function(params) { - input_id <- paste0("base_", params$id) - params$id <- NULL - if (params$type %in% "select_variables") { - params$data <- data() - } - - create_input_element(params, ns, input_id) - }) - tagList(base_inputs) - - }) - ### Color option output$color_palette <- shiny::renderUI({ # shiny::req(input$type) @@ -285,49 +250,19 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { shiny::observeEvent(input$act_plot, { if (NROW(data()) > 0) { - tryCatch({ - # Get all input values with prefixes - base_inputs <- reactiveValuesToList(input)[grep("^base_", names(reactiveValuesToList(input)))] - # advanced_inputs <- reactiveValuesToList(input)[grep("^advanced_", names(reactiveValuesToList(input)))] - - # Remove the prefix from names - names(base_inputs) <- gsub("^base_", "", names(base_inputs)) - # names(advanced_inputs) <- gsub("^advanced_", "", names(advanced_inputs)) - - base_inputs <- c(base_inputs, - list(color.palette = input$color_palette)) - - # If any of the specified parameters are NULL/missing, the settings - # accordion/panel was never opened, and they can be ignored, as - # default settings will the be used. - if (any(sapply(base_inputs, is.null))) { - dynamic_params <- list() - } else { - dynamic_params <- base_inputs - } - - # Build parameters for plotting function + tryCatch({ parameters <- list( type = rv$plot.params()[["fun"]], pri = input$primary, sec = input$secondary, - ter = input$tertiary + ter = input$tertiary, + color.palette = input$color_palette ) - parameters <- modifyList(parameters, dynamic_params) - ## If the dictionary holds additional arguments to pass to the ## plotting function, these are included if (!is.null(rv$plot.params()[["fun.args"]])) { - default_params <- rv$plot.params()[["fun.args"]] - - ## Ensure not to overwrite user defined parameters are overwritten - ## This allows to define default parameters. - ## - ## This will create a strange edge case, where the plot looks in - ## one way, when plotted initially, but may change, when the settings - ## accordion is opened. Problem for future me. Really mostly an edge case. - parameters <- modifyList(parameters, default_params[!names(default_params) %in% names(parameters)]) + parameters <- modifyList(parameters, rv$plot.params()[["fun.args"]]) } shiny::withProgress(message = i18n$t("Drawing the plot. Hold tight for a moment.."), @@ -363,25 +298,7 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { if (!is.null(rv$plot)) { rv$plot } else { - # Create a placeholder plot with instructions using ggplot2 - ggplot2::ggplot() + - ggplot2::annotate( - "text", - x = 0.5, - y = 0.5, - label = i18n$t("Select variables and plot type,\nthen click 'Plot' to generate visualization"), - size = 5, - color = "gray50", - lineheight = 0.8 - ) + - ggplot2::xlim(0, 1) + - ggplot2::ylim(0, 1) + - ggplot2::theme_void() + - ggplot2::theme( - panel.background = ggplot2::element_rect(fill = "white"), - plot.background = ggplot2::element_rect(fill = "white") - ) - # return(NULL) + return(NULL) } }) @@ -425,3 +342,503 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { } ) } + +#' Select all from vector but +#' +#' @param data vector +#' @param ... exclude +#' +#' @returns vector +#' @export +#' +#' @examples +#' all_but(1:10, c(2, 3), 11, 5) +all_but <- function(data, ...) { + data[!data %in% c(...)] +} + +#' Easily subset by data type function +#' +#' @param data data +#' @param types desired types +#' @param type.fun function to get type. Default is outcome_type +#' +#' @returns vector +#' @export +#' +#' @examples +#' default_parsing(mtcars) |> subset_types("ordinal") +#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical")) +#' #' default_parsing(mtcars) |> subset_types("factor",class) +subset_types <- function(data, types, type.fun = data_type) { + data[sapply(data, type.fun) %in% types] +} + + +#' Implemented functions +#' +#' @description +#' Library of supported functions. The list name and "descr" element should be +#' unique for each element on list. +#' +#' - descr: Plot description +#' +#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal) +#' +#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal) +#' +#' - secondary.extra: "none" or NULL to have option to choose none. +#' +#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal) +#' +#' +#' @returns list +#' @export +#' +#' @examples +#' supported_plots() |> str() +supported_plots <- function() { + list( + plot_bar_rel = list( + fun = "plot_bar", + fun.args = list(style = "fill"), + descr = i18n$t("Stacked relative barplot"), + note = i18n$t( + "Create relative stacked barplots to show the distribution of categorical levels" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ), + plot_bar_abs = list( + fun = "plot_bar", + fun.args = list(style = "dodge"), + descr = i18n$t("Side-by-side barplot"), + note = i18n$t( + "Create side-by-side barplot to show the distribution of categorical levels" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_hbars = list( + fun = "plot_hbars", + descr = i18n$t("Stacked horizontal bars"), + note = i18n$t( + "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_violin = list( + fun = "plot_violin", + descr = i18n$t("Violin plot"), + note = i18n$t( + "A modern alternative to the classic boxplot to visualise data distribution" + ), + primary.type = c("datatime", "continuous"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + secondary.extra = "none", + tertiary.type = c("dichotomous", "categorical") + ), + # plot_ridge = list( + # descr = "Ridge plot", + # note = "An alternative option to visualise data distribution", + # primary.type = "continuous", + # secondary.type = c("dichotomous" ,"categorical"), + # tertiary.type = c("dichotomous" ,"categorical"), + # secondary.extra = NULL + # ), + plot_sankey = list( + fun = "plot_sankey", + descr = i18n$t("Sankey plot"), + note = i18n$t("A way of visualising change between groups"), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + secondary.extra = NULL, + tertiary.type = c("dichotomous", "categorical") + ), + plot_scatter = list( + fun = "plot_scatter", + descr = i18n$t("Scatter plot"), + note = i18n$t("A classic way of showing the association between to variables"), + primary.type = c("datatime", "continuous"), + secondary.type = c("datatime", "continuous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ), + plot_box = list( + fun = "plot_box", + descr = i18n$t("Box plot"), + note = i18n$t("A classic way to plot data distribution by groups"), + primary.type = c("datatime", "continuous"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_euler = list( + fun = "plot_euler", + descr = i18n$t("Euler diagram"), + note = i18n$t( + "Generate area-proportional Euler diagrams to display set relationships" + ), + primary.type = c("dichotomous"), + secondary.type = c("dichotomous"), + secondary.multi = TRUE, + secondary.max = 4, + tertiary.type = c("dichotomous"), + secondary.extra = NULL + ), + plot_euler = list( + fun = "plot_likert", + descr = i18n$t("Likert diagram"), + note = i18n$t( + "Plot survey results" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = TRUE, + secondary.extra = NULL, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ) + ) +} + +#' Get possible regression models +#' +#' @param data data +#' +#' @returns character vector +#' @export +#' +#' @examples +#' mtcars |> +#' default_parsing() |> +#' dplyr::pull("cyl") |> +#' possible_plots() +#' +#' mtcars |> +#' default_parsing() |> +#' dplyr::select("mpg") |> +#' possible_plots() +possible_plots <- function(data) { + # browser() + # data <- if (is.reactive(data)) data() else data + if (is.data.frame(data)) { + data <- data[[1]] + } + + type <- data_type(data) + + if (type == "unknown") { + out <- type + } else { + out <- supported_plots() |> + lapply(\(.x) { + if (type %in% .x$primary.type) { + .x$descr + } + }) |> + unlist() + } + unname(out) +} + +#' Get the function options based on the selected function description +#' +#' @param data vector +#' +#' @returns list +#' @export +#' +#' @examples +#' ls <- mtcars |> +#' default_parsing() |> +#' dplyr::pull(mpg) |> +#' possible_plots() |> +#' (\(.x){ +#' .x[[1]] +#' })() |> +#' get_plot_options() +get_plot_options <- function(data) { + descrs <- supported_plots() |> + lapply(\(.x) { + .x$descr + }) |> + unlist() + supported_plots() |> + (\(.x) { + .x[match(data, descrs)] + })() +} + + + +#' Wrapper to create plot based on provided type +#' +#' @param data data.frame +#' @param pri primary variable +#' @param sec secondary variable +#' @param ter tertiary variable +#' @param type plot type (derived from possible_plots() and matches custom function) +#' @param color.palette choose color palette. See \code{\link{plot_colors}} for support. +#' @param ... ignored for now +#' +#' @name data-plots +#' +#' @returns ggplot2 object +#' @export +#' +#' @examples +#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes() +create_plot <- function(data, + type, + pri, + sec, + ter = NULL, + color.palette = "viridis", + ...) { + if (!is.null(sec)) { + if (!any(sec %in% names(data))) { + sec <- NULL + } + } + + if (!is.null(ter)) { + if (!ter %in% names(data)) { + ter <- NULL + } + } + + parameters <- list( + pri = pri, + sec = sec, + ter = ter, + color.palette = color.palette, + ... + ) + + out <- do.call(type, modifyList(parameters, list(data = data))) + + code <- rlang::call2(type, !!!parameters, .ns = "FreesearchR") + + attr(out, "code") <- code + out +} + +#' Print label, and if missing print variable name for plots +#' +#' @param data vector or data frame +#' @param var variable name. Optional. +#' +#' @returns character string +#' @export +#' +#' @examples +#' mtcars |> get_label(var = "mpg") +#' mtcars |> get_label() +#' mtcars$mpg |> get_label() +#' gtsummary::trial |> get_label(var = "trt") +#' gtsummary::trial$trt |> get_label() +#' 1:10 |> get_label() +get_label <- function(data, var = NULL) { + # data <- if (is.reactive(data)) data() else data + if (!is.null(var) & is.data.frame(data)) { + data <- data[[var]] + } + out <- REDCapCAST::get_attr(data = data, attr = "label") + if (is.na(out)) { + if (is.null(var)) { + out <- deparse(substitute(data)) + } else { + if (is.symbol(var)) { + out <- gsub('\"', "", deparse(substitute(var))) + } else { + out <- var + } + } + } + out +} + + +#' Line breaking at given number of characters for nicely plotting labels +#' +#' @param data string +#' @param lineLength maximum line length +#' @param fixed flag to force split at exactly the value given in lineLength. +#' Default is FALSE, only splitting at spaces. +#' +#' @returns character string +#' @export +#' +#' @examples +#' "Lorem ipsum... you know the routine" |> line_break() +#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE) +line_break <- function(data, + lineLength = 20, + force = FALSE) { + if (isTRUE(force)) { + ## This eats some letters when splitting a sentence... ?? + gsub(paste0("(.{1,", lineLength, "})(\\s|[[:alnum:]])"), + "\\1\n", + data) + } else { + paste(strwrap(data, lineLength), collapse = "\n") + } + ## https://stackoverflow.com/a/29847221 +} + + +#' Wrapping +#' +#' @param data list of ggplot2 objects +#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL +#' @param title panel title +#' @param guides passed to patchwork::wrap_plots() +#' @param axes passed to patchwork::wrap_plots() +#' @param axis_titles passed to patchwork::wrap_plots() +#' @param ... passed to patchwork::wrap_plots() +#' +#' @returns list of ggplot2 objects +#' @export +#' +wrap_plot_list <- function(data, + tag_levels = NULL, + title = NULL, + axis.font.family = NULL, + guides = "collect", + axes = "collect", + axis_titles = "collect", + y.axis.percentage = FALSE, + ...) { + if (ggplot2::is_ggplot(data[[1]])) { + if (length(data) > 1) { + out <- data |> + (\(.x) { + if (rlang::is_named(.x)) { + purrr::imap(.x, \(.y, .i) { + .y + ggplot2::ggtitle(.i) + }) + } else { + .x + } + })() |> + align_axes(percentage=y.axis.percentage) |> + patchwork::wrap_plots(guides = guides, + axes = axes, + axis_titles = axis_titles, + ...) + if (!is.null(tag_levels)) { + out <- out + patchwork::plot_annotation(tag_levels = tag_levels) + } + if (!is.null(title)) { + out <- out + + patchwork::plot_annotation( + title = title, + theme = ggplot2::theme(plot.title = ggplot2::element_text(size = 25)) + ) + } + } else { + out <- data[[1]] + } + } else { + cli::cli_abort("Can only wrap lists of {.cls ggplot} objects") + } + + if (!is.null(axis.font.family)) { + if (inherits(x = out, what = "patchwork")) { + out <- out & + ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) + } else { + out <- out + + ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) + } + } + + out +} + + +#' Aligns axes between plots +#' +#' @param ... ggplot2 objects or list of ggplot2 objects +#' +#' @returns list of ggplot2 objects +#' @export +#' +align_axes <- function(..., + x.axis = TRUE, + y.axis = TRUE, + percentage = FALSE) { + # https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object + # https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150 + if (ggplot2::is_ggplot(..1)) { + ## Assumes list of ggplots + p <- list(...) + } else if (is.list(..1)) { + ## Assumes list with list of ggplots + p <- ..1 + } else { + cli::cli_abort("Can only align {.cls ggplot} objects or a list of them") + } + + yr <- clean_common_axis(p, "y") + + xr <- clean_common_axis(p, "x") + + suppressWarnings({ + p_out <- purrr::map(p, \(.x) { + out <- .x + if (isTRUE(x.axis)) { + out <- out + ggplot2::xlim(xr) + } + if (isTRUE(y.axis)) { + out <- out + ggplot2::ylim(yr) + } + out + }) + }) + + if(isTRUE(percentage)){ + lapply(p_out,\(.x){ + .x+ + ggplot2::scale_y_continuous(labels = scales::percent) + }) + } else { + p_out + } +} + +#' Extract and clean axis ranges +#' +#' @param p plot +#' @param axis axis. x or y. +#' +#' @returns vector +#' @export +#' +clean_common_axis <- function(p, axis) { + purrr::map(p, ~ ggplot2::layer_scales(.x)[[axis]]$get_limits()) |> + unlist() |> + (\(.x) { + if (is.numeric(.x)) { + range(.x) + } else { + as.character(.x) + } + })() |> + unique() +} diff --git a/R/hosted_version.R b/R/hosted_version.R index 27a50899..33aaf67c 100644 --- a/R/hosted_version.R +++ b/R/hosted_version.R @@ -1 +1 @@ -hosted_version <- function()'v26.6.1' +hosted_version <- function()'v26.4.2-260410' diff --git a/R/plot-helpers.R b/R/plot-helpers.R deleted file mode 100644 index 5b4ae981..00000000 --- a/R/plot-helpers.R +++ /dev/null @@ -1,878 +0,0 @@ -#' Implemented functions -#' -#' @description -#' Library of supported functions. The list name and "descr" element should be -#' unique for each element on list. -#' -#' - fun: the plotting function -#' -#' - fun.args: default parameters for the plotting function -#' -#' - descr: Plot description -#' -#' - note: Short note/description of the function for displaying in ui and docs -#' -#' - primary.type: Primary variable data type (see [data_type]) -#' -#' - base: holds a list of parameters for plot input fields generation -#' Secondary and tertiary variable input fields are mandatory. -#' -#' -#' @returns list -#' @export -#' -#' @examples -#' available_plots() |> str() -available_plots <- function() { - list( - plot_bar_rel = list( - fun = "plot_bar", - fun.args = list(style = "fill"), - descr = i18n$t("Stacked relative barplot"), - note = i18n$t( - "Create relative stacked barplots to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Additional variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_bar_abs = list( - fun = "plot_bar", - fun.args = list(style = "dodge"), - descr = i18n$t("Side-by-side barplot"), - note = i18n$t( - "Create side-by-side barplot to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_hbars = list( - fun = "plot_hbars", - descr = i18n$t("Stacked horizontal bars"), - note = i18n$t( - "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ), - list( - id = "reverse", - type = "select_input", - label = i18n$t("Reverse colors"), - choices = c(yes = TRUE, no = FALSE) - ) - ), - advanced = list() - ######### - ), - plot_violin = list( - fun = "plot_violin", - descr = i18n$t("Violin plot"), - note = i18n$t( - "A modern alternative to the classic boxplot to visualise data distribution" - ), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_sankey = list( - fun = "plot_sankey", - descr = i18n$t("Sankey plot"), - note = i18n$t("A way of visualising change between groups"), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_scatter = list( - fun = "plot_scatter", - descr = i18n$t("Scatter plot"), - note = i18n$t("A classic way of showing the association between to variables"), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("datatime", "continuous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_box = list( - fun = "plot_box", - descr = i18n$t("Box plot"), - note = i18n$t("A classic way to plot data distribution by groups"), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_euler = list( - fun = "plot_euler", - descr = i18n$t("Euler diagram"), - note = i18n$t( - "Generate area-proportional Euler diagrams to display set relationships" - ), - primary.type = c("dichotomous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = TRUE, - maxItems = 4 - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_likert = list( - fun = "plot_likert", - descr = i18n$t("Likert diagram"), - note = i18n$t("Plot survey results"), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Additional variables"), - multiple = TRUE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ) - ) -} - -# Helper function to create input elements dynamically -create_input_element <- function(params, ns, input_id) { - # Add the namespaced inputId to the arguments - params$inputId <- ns(input_id) - - # Map input types to Shiny functions - input_function <- switch( - params$type, - "numeric_input" = shiny::numericInput, - "select_input" = shiny::selectInput, - "checkbox_input" = shiny::checkboxInput, - "slider_input" = shiny::sliderInput, - "text_input" = shiny::textInput, - "select_variables" = selectPlotVariables - ) - - params$type <- NULL - params$id <- NULL - - - # Call the function with all arguments - do.call(input_function, params) -} - -#' Wrapper for columnSelectInput -#' -selectPlotVariables <- function(data, - exclude = NULL, - allow_none = TRUE, - var_types, - ...) { - datar <- if (is.reactive(data)) { - data - } else { - reactive(data) - } - - cols <- all_but(colnames(subset_types(datar(), var_types)), exclude) - - if (isTRUE(allow_none)) { - cols <- c("none", cols) - } - - params <- list(...) - - params$none_label <- i18n$t("No variable") - params$col_subset <- cols - - rlang::exec(columnSelectInput, !!!append_list(datar(), params, "data")) -} - - - -#' Select all from vector but -#' -#' @param data vector -#' @param ... exclude -#' -#' @returns vector -#' @export -#' -#' @examples -#' all_but(1:10, c(2, 3), 11, 5) -all_but <- function(data, ...) { - data[!data %in% c(...)] -} - -#' Easily subset by data type function -#' -#' @param data data -#' @param types desired types -#' @param type.fun function to get type. Default is outcome_type -#' -#' @returns vector -#' @export -#' -#' @examples -#' default_parsing(mtcars) |> subset_types("ordinal") -#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical")) -#' #' default_parsing(mtcars) |> subset_types("factor",class) -subset_types <- function(data, types, type.fun = data_type) { - data[sapply(data, type.fun) %in% types] -} - - -#' Implemented functions -#' -#' @description -#' Library of supported functions. The list name and "descr" element should be -#' unique for each element on list. -#' -#' - descr: Plot description -#' -#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal) -#' -#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal) -#' -#' - secondary.extra: "none" or NULL to have option to choose none. -#' -#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal) -#' -#' -#' @returns list -#' @export -#' -#' @examples -#' supported_plots() |> str() -supported_plots <- function() { - list( - plot_bar_rel = list( - fun = "plot_bar", - fun.args = list(style = "fill"), - descr = i18n$t("Stacked relative barplot"), - note = i18n$t( - "Create relative stacked barplots to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ), - plot_bar_abs = list( - fun = "plot_bar", - fun.args = list(style = "dodge"), - descr = i18n$t("Side-by-side barplot"), - note = i18n$t( - "Create side-by-side barplot to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_hbars = list( - fun = "plot_hbars", - descr = i18n$t("Stacked horizontal bars"), - note = i18n$t( - "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_violin = list( - fun = "plot_violin", - descr = i18n$t("Violin plot"), - note = i18n$t( - "A modern alternative to the classic boxplot to visualise data distribution" - ), - primary.type = c("datatime", "continuous"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - secondary.extra = "none", - tertiary.type = c("dichotomous", "categorical") - ), - # plot_ridge = list( - # descr = "Ridge plot", - # note = "An alternative option to visualise data distribution", - # primary.type = "continuous", - # secondary.type = c("dichotomous" ,"categorical"), - # tertiary.type = c("dichotomous" ,"categorical"), - # secondary.extra = NULL - # ), - plot_sankey = list( - fun = "plot_sankey", - descr = i18n$t("Sankey plot"), - note = i18n$t("A way of visualising change between groups"), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - secondary.extra = NULL, - tertiary.type = c("dichotomous", "categorical") - ), - plot_scatter = list( - fun = "plot_scatter", - descr = i18n$t("Scatter plot"), - note = i18n$t("A classic way of showing the association between to variables"), - primary.type = c("datatime", "continuous"), - secondary.type = c("datatime", "continuous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ), - plot_box = list( - fun = "plot_box", - descr = i18n$t("Box plot"), - note = i18n$t("A classic way to plot data distribution by groups"), - primary.type = c("datatime", "continuous"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_euler = list( - fun = "plot_euler", - descr = i18n$t("Euler diagram"), - note = i18n$t( - "Generate area-proportional Euler diagrams to display set relationships" - ), - primary.type = c("dichotomous"), - secondary.type = c("dichotomous"), - secondary.multi = TRUE, - secondary.max = 4, - tertiary.type = c("dichotomous"), - secondary.extra = NULL - ), - plot_likert = list( - fun = "plot_likert", - descr = i18n$t("Likert diagram"), - note = i18n$t("Plot survey results"), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = TRUE, - secondary.extra = NULL, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ) - ) -} - -#' Get possible regression models -#' -#' @param data data -#' -#' @returns character vector -#' @export -#' -#' @examples -#' mtcars |> -#' default_parsing() |> -#' dplyr::pull("cyl") |> -#' possible_plots() -#' -#' mtcars |> -#' default_parsing() |> -#' dplyr::select("mpg") |> -#' possible_plots() -possible_plots <- function(data, source_list = supported_plots()) { - # browser() - # data <- if (is.reactive(data)) data() else data - if (is.data.frame(data)) { - data <- data[[1]] - } - - type <- data_type(data) - - if (type == "unknown") { - out <- type - } else { - out <- source_list |> - lapply(\(.x) { - if (type %in% .x$primary.type) { - .x$descr - } - }) |> - unlist() - } - unname(out) -} - -#' Get the function options based on the selected function description -#' -#' @param data vector -#' -#' @returns list -#' @export -#' -#' @examples -#' ls <- mtcars |> -#' default_parsing() |> -#' dplyr::pull(mpg) |> -#' possible_plots() |> -#' (\(.x){ -#' .x[[1]] -#' })() |> -#' get_plot_options() -get_plot_options <- function(data) { - descrs <- supported_plots() |> - lapply(\(.x) { - .x$descr - }) |> - unlist() - supported_plots() |> - (\(.x) { - .x[match(data, descrs)] - })() -} - -#' Get the function parameters based on the selected function description -#' -#' @param data vector -#' -#' @returns list -#' @export -#' -#' @examples -#' ls <- mtcars |> -#' default_parsing() |> -#' dplyr::pull(mpg) |> -#' possible_plots() |> -#' (\(.x){ -#' .x[[1]] -#' })() |> -#' get_input_params() -get_input_params <- function(data) { - descr <- available_plots() |> - lapply(\(.x) { - .x$descr - }) |> - unlist() - available_plots() |> - (\(.x) { - .x[match(data, descr)] - })() -} - - -#' Wrapper to create plot based on provided type -#' -#' @param data data.frame -#' @param pri primary variable -#' @param sec secondary variable -#' @param ter tertiary variable -#' @param type plot type (derived from possible_plots() and matches custom function) -#' @param color.palette choose color palette. See \code{\link{plot_colors}} for support. -#' @param ... ignored for now -#' -#' @name data-plots -#' -#' @returns ggplot2 object -#' @export -#' -#' @examples -#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes() -create_plot <- function(data, - type, - pri, - sec, - ter = NULL, - color.palette = "viridis", - ...) { - if (!is.null(sec)) { - if (!any(sec %in% names(data))) { - sec <- NULL - } - } - - if (!is.null(ter)) { - if (!ter %in% names(data)) { - ter <- NULL - } - } - - parameters <- list( - pri = pri, - sec = sec, - ter = ter, - color.palette = color.palette, - ... - ) - - out <- do.call(type, modifyList(parameters, list(data = data))) - - code <- rlang::call2(type, !!!parameters, .ns = "FreesearchR") - - attr(out, "code") <- code - out -} - -#' Print label, and if missing print variable name for plots -#' -#' @param data vector or data frame -#' @param var variable name. Optional. -#' -#' @returns character string -#' @export -#' -#' @examples -#' mtcars |> get_label(var = "mpg") -#' mtcars |> get_label() -#' mtcars$mpg |> get_label() -#' gtsummary::trial |> get_label(var = "trt") -#' gtsummary::trial$trt |> get_label() -#' 1:10 |> get_label() -get_label <- function(data, var = NULL) { - # data <- if (is.reactive(data)) data() else data - if (!is.null(var) & is.data.frame(data)) { - data <- data[[var]] - } - out <- REDCapCAST::get_attr(data = data, attr = "label") - if (is.na(out)) { - if (is.null(var)) { - out <- deparse(substitute(data)) - } else { - if (is.symbol(var)) { - out <- gsub('\"', "", deparse(substitute(var))) - } else { - out <- var - } - } - } - out -} - - -#' Line breaking at given number of characters for nicely plotting labels -#' -#' @param data string -#' @param lineLength maximum line length -#' @param fixed flag to force split at exactly the value given in lineLength. -#' Default is FALSE, only splitting at spaces. -#' -#' @returns character string -#' @export -#' -#' @examples -#' "Lorem ipsum... you know the routine" |> line_break() -#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE) -line_break <- function(data, - lineLength = 20, - force = FALSE) { - if (isTRUE(force)) { - ## This eats some letters when splitting a sentence... ?? - gsub(paste0("(.{1,", lineLength, "})(\\s|[[:alnum:]])"), - "\\1\n", - data) - } else { - paste(strwrap(data, lineLength), collapse = "\n") - } - ## https://stackoverflow.com/a/29847221 -} - - -#' Wrapping -#' -#' @param data list of ggplot2 objects -#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL -#' @param title panel title -#' @param guides passed to patchwork::wrap_plots() -#' @param axes passed to patchwork::wrap_plots() -#' @param axis_titles passed to patchwork::wrap_plots() -#' @param ... passed to patchwork::wrap_plots() -#' -#' @returns list of ggplot2 objects -#' @export -#' -wrap_plot_list <- function(data, - tag_levels = NULL, - title = NULL, - axis.font.family = NULL, - guides = "collect", - axes = "collect", - axis_titles = "collect", - y.axis.percentage = FALSE, - ...) { - if (ggplot2::is_ggplot(data[[1]])) { - if (length(data) > 1) { - out <- data |> - (\(.x) { - if (rlang::is_named(.x)) { - purrr::imap(.x, \(.y, .i) { - .y + ggplot2::ggtitle(.i) - }) - } else { - .x - } - })() |> - align_axes(percentage = y.axis.percentage) |> - patchwork::wrap_plots(guides = guides, - axes = axes, - axis_titles = axis_titles, - ...) - if (!is.null(tag_levels)) { - out <- out + patchwork::plot_annotation(tag_levels = tag_levels) - } - if (!is.null(title)) { - out <- out + - patchwork::plot_annotation( - title = title, - theme = ggplot2::theme(plot.title = ggplot2::element_text(size = 25)) - ) - } - } else { - out <- data[[1]] - } - } else { - cli::cli_abort("Can only wrap lists of {.cls ggplot} objects") - } - - if (!is.null(axis.font.family)) { - if (inherits(x = out, what = "patchwork")) { - out <- out & - ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) - } else { - out <- out + - ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) - } - } - - out -} - - -#' Aligns axes between plots -#' -#' @param ... ggplot2 objects or list of ggplot2 objects -#' -#' @returns list of ggplot2 objects -#' @export -#' -align_axes <- function(..., - x.axis = TRUE, - y.axis = TRUE, - percentage = FALSE) { - # https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object - # https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150 - if (ggplot2::is_ggplot(..1)) { - ## Assumes list of ggplots - p <- list(...) - } else if (is.list(..1)) { - ## Assumes list with list of ggplots - p <- ..1 - } else { - cli::cli_abort("Can only align {.cls ggplot} objects or a list of them") - } - - yr <- clean_common_axis(p, "y") - - xr <- clean_common_axis(p, "x") - - suppressWarnings({ - p_out <- purrr::map(p, \(.x) { - out <- .x - if (isTRUE(x.axis)) { - out <- out + ggplot2::xlim(xr) - } - if (isTRUE(y.axis)) { - out <- out + ggplot2::ylim(yr) - } - out - }) - }) - - if (isTRUE(percentage)) { - lapply(p_out, \(.x) { - .x + - ggplot2::scale_y_continuous(labels = scales::percent) - }) - } else { - p_out - } -} - -#' Extract and clean axis ranges -#' -#' @param p plot -#' @param axis axis. x or y. -#' -#' @returns vector -#' @export -#' -clean_common_axis <- function(p, axis) { - purrr::map(p, ~ ggplot2::layer_scales(.x)[[axis]]$get_limits()) |> - unlist() |> - (\(.x) { - if (is.numeric(.x)) { - range(.x) - } else { - as.character(.x) - } - })() |> - unique() -} diff --git a/R/plot_bar.R b/R/plot_bar.R index e9879ef3..0535b6f3 100644 --- a/R/plot_bar.R +++ b/R/plot_bar.R @@ -39,14 +39,14 @@ plot_bar <- function(data, sec = sec, style = style, max_level = max_level, - color.palette = color.palette, - ... + color.palette = color.palette ) }) wrap_plot_list(out, title = glue::glue(i18n$t("Grouped by {get_label(data,ter)}")), - y.axis.percentage = TRUE) + y.axis.percentage = TRUE, + ...) } diff --git a/R/plot_box.R b/R/plot_box.R index 4acd67ab..01911aac 100644 --- a/R/plot_box.R +++ b/R/plot_box.R @@ -32,11 +32,11 @@ plot_box <- function(data, pri, sec, ter = NULL,color.palette="viridis",...) { data = .ds, pri = pri, sec = sec, - color.palette=color.palette, ... + color.palette=color.palette ) }) - wrap_plot_list(out,title=glue::glue(i18n$t("Grouped by {get_label(data,ter)}"))) + wrap_plot_list(out,title=glue::glue(i18n$t("Grouped by {get_label(data,ter)}")),...) } diff --git a/R/plot_euler.R b/R/plot_euler.R index a5a0d31f..27cdf02f 100644 --- a/R/plot_euler.R +++ b/R/plot_euler.R @@ -131,7 +131,7 @@ plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103,color.palette="vi #' D = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE) #' ) |> plot_euler_single() #' mtcars[c("vs", "am")] |> plot_euler_single("magma") -plot_euler_single <- function(data,color.palette="viridis", ...) { +plot_euler_single <- function(data,color.palette="viridis") { data |> ggeulerr(shape = "circle") + diff --git a/R/plot_hbar.R b/R/plot_hbar.R index fc33b20d..d93ef4c9 100644 --- a/R/plot_hbar.R +++ b/R/plot_hbar.R @@ -15,15 +15,13 @@ plot_hbars <- function(data, pri, sec, ter = NULL, - color.palette = "viridis", - ...) { + color.palette = "viridis") { vertical_stacked_bars( data = data, score = pri, group = sec, strata = ter, - color.palette = color.palette, - ... + color.palette = color.palette ) } @@ -76,7 +74,7 @@ vertical_stacked_bars <- function(data, colors <- generate_colors(n = nrow(df.table), palette = color.palette) ## Colors are reversed by default as that usually gives the best result - if (isTRUE(reverse) | reverse=="TRUE") { + if (isTRUE(reverse)) { colors <- rev(colors) } diff --git a/R/plot_likert.R b/R/plot_likert.R index e33256a2..c18c57a1 100644 --- a/R/plot_likert.R +++ b/R/plot_likert.R @@ -15,8 +15,7 @@ plot_likert <- function(data, pri, sec = NULL, ter = NULL, - color.palette = "viridis", - ...) { + color.palette = "viridis") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { diff --git a/R/plot_sankey.R b/R/plot_sankey.R index 409a1050..23c1a13a 100644 --- a/R/plot_sankey.R +++ b/R/plot_sankey.R @@ -95,8 +95,7 @@ plot_sankey <- function(data, default.color = "#2986cc", box.color = "#1E4B66", na.color = "grey80", - missing.level = "Missing", - ...) { + missing.level = "Missing") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { diff --git a/R/plot_scatter.R b/R/plot_scatter.R index 8c73547e..142c30fd 100644 --- a/R/plot_scatter.R +++ b/R/plot_scatter.R @@ -8,7 +8,7 @@ #' @examples #' mtcars |> plot_scatter(pri = "mpg", sec = "wt") #' mtcars |> plot_scatter(pri = "mpg", sec = "wt",ter="carb") -plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis", ...) { +plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis") { if (is.null(ter)) { rempsyc::nice_scatter( data = data, diff --git a/R/plot_violin.R b/R/plot_violin.R index 29850d26..83d11d2a 100644 --- a/R/plot_violin.R +++ b/R/plot_violin.R @@ -8,7 +8,7 @@ #' @examples #' mtcars |> plot_violin(pri = "mpg", sec = "cyl") #' mtcars |> plot_violin(pri = "mpg", sec = "cyl", ter = "gear", color.palette="Blues") -plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis", ...) { +plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -23,8 +23,7 @@ plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis", ... group = sec, response = pri, xtitle = get_label(data, var = sec), - ytitle = get_label(data, var = pri), - ... + ytitle = get_label(data, var = pri) )+ scale_fill_generate(palette=color.palette) }) diff --git a/R/sysdata.rda b/R/sysdata.rda index 1829eab4..c56ca282 100644 Binary files a/R/sysdata.rda and b/R/sysdata.rda differ diff --git a/R/ui_elements.R b/R/ui_elements.R index b08d5152..6686879d 100644 --- a/R/ui_elements.R +++ b/R/ui_elements.R @@ -247,7 +247,7 @@ ui_elements <- function(selection) { "Read more on how ", tags$a( "data types", - href = "https://freesearchr.github.io/FreesearchR-knowledge/app/data_types.html", + href = "https://agdamsbo.github.io/FreesearchR/articles/data-types.html", target = "_blank", rel = "noopener noreferrer" ), @@ -694,7 +694,7 @@ ui_elements <- function(selection) { "docs" = bslib::nav_item( # shiny::img(shiny::icon("book")), shiny::tags$a( - href = "https://freesearchr.github.io/FreesearchR-knowledge/", + href = "https://agdamsbo.github.io/FreesearchR/", "Docs", shiny::icon("arrow-up-right-from-square"), target = "_blank", diff --git a/SESSION.md b/SESSION.md index 55c29962..1e301770 100644 --- a/SESSION.md +++ b/SESSION.md @@ -1,21 +1,21 @@ -------------------------------------------------------------------------------- -------------------------------- R environment --------------------------------- -------------------------------------------------------------------------------- -|setting |value | -|:-----------|:--------------------------------------------------------------------------------------------------| -|version |R version 4.5.2 (2025-10-31) | -|os |macOS Tahoe 26.5 | -|system |aarch64, darwin20 | -|ui |RStudio | -|language |(EN) | -|collate |en_US.UTF-8 | -|ctype |en_US.UTF-8 | -|tz |Europe/Copenhagen | -|date |2026-06-01 | -|rstudio |2026.04.0+526 Globemaster Allium (desktop) | -|pandoc |3.8.3 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown) | -|quarto |1.9.37 @ /usr/local/bin/quarto | -|FreesearchR |26.6.1.260601 | +|setting |value | +|:-----------|:------------------------------------------| +|version |R version 4.5.2 (2025-10-31) | +|os |macOS Tahoe 26.4.1 | +|system |aarch64, darwin20 | +|ui |RStudio | +|language |(EN) | +|collate |en_US.UTF-8 | +|ctype |en_US.UTF-8 | +|tz |Europe/Copenhagen | +|date |2026-04-10 | +|rstudio |2026.01.1+403 Apple Blossom (desktop) | +|pandoc |3.6.4 @ /opt/homebrew/bin/ (via rmarkdown) | +|quarto |1.7.30 @ /usr/local/bin/quarto | +|FreesearchR |26.4.2.260410 | -------------------------------------------------------------------------------- @@ -26,8 +26,6 @@ |apexcharter |0.4.5 |2026-01-07 |CRAN (R 4.5.2) | |askpass |1.2.1 |2024-10-04 |CRAN (R 4.5.0) | |assertthat |0.2.1 |2019-03-21 |CRAN (R 4.5.0) | -|attachment |0.4.5 |2025-03-14 |CRAN (R 4.5.0) | -|attempt |0.3.1 |2020-05-03 |CRAN (R 4.5.0) | |backports |1.5.0 |2024-05-23 |CRAN (R 4.5.0) | |base64enc |0.1-6 |2026-02-02 |CRAN (R 4.5.2) | |bayestestR |0.17.0 |2025-08-29 |CRAN (R 4.5.0) | @@ -46,7 +44,6 @@ |cardx |0.3.2 |2026-02-05 |CRAN (R 4.5.2) | |caTools |1.18.3 |2024-09-04 |CRAN (R 4.5.0) | |cellranger |1.1.0 |2016-07-27 |CRAN (R 4.5.0) | -|cffr |1.2.1 |2026-01-12 |CRAN (R 4.5.2) | |checkmate |2.3.4 |2026-02-03 |CRAN (R 4.5.2) | |class |7.3-23 |2025-01-01 |CRAN (R 4.5.0) | |classInt |0.4-11 |2025-01-08 |CRAN (R 4.5.0) | @@ -64,7 +61,6 @@ |devtools |2.4.6 |2025-10-03 |CRAN (R 4.5.0) | |DHARMa |0.4.7 |2024-10-18 |CRAN (R 4.5.0) | |digest |0.6.39 |2025-11-19 |CRAN (R 4.5.2) | -|dockerfiler |0.2.5 |2025-05-07 |CRAN (R 4.5.0) | |doParallel |1.0.17 |2022-02-07 |CRAN (R 4.5.0) | |dplyr |1.2.0 |2026-02-03 |CRAN (R 4.5.2) | |DT |0.34.0 |2025-09-02 |CRAN (R 4.5.0) | @@ -87,7 +83,7 @@ |foreach |1.5.2 |2022-02-02 |CRAN (R 4.5.0) | |foreign |0.8-91 |2026-01-29 |CRAN (R 4.5.2) | |Formula |1.2-5 |2023-02-24 |CRAN (R 4.5.0) | -|FreesearchR |26.6.1 |NA |NA | +|FreesearchR |26.4.2 |NA |NA | |fs |1.6.7 |2026-03-06 |CRAN (R 4.5.2) | |gdtools |0.5.0 |2026-02-09 |CRAN (R 4.5.2) | |generics |0.1.4 |2025-05-09 |CRAN (R 4.5.0) | @@ -97,7 +93,7 @@ |ggplot2 |4.0.2 |2026-02-03 |CRAN (R 4.5.2) | |ggridges |0.5.7 |2025-08-27 |CRAN (R 4.5.0) | |ggstats |0.13.0 |2026-03-06 |CRAN (R 4.5.2) | -|glue |1.8.0 |2024-09-30 |CRAN (R 4.5.2) | +|glue |1.8.0 |2024-09-30 |CRAN (R 4.5.0) | |gridExtra |2.3 |2017-09-09 |CRAN (R 4.5.0) | |gt |1.3.0 |2026-01-22 |CRAN (R 4.5.2) | |gtable |0.3.6 |2024-10-25 |CRAN (R 4.5.0) | @@ -128,7 +124,6 @@ |MASS |7.3-65 |2025-02-28 |CRAN (R 4.5.0) | |Matrix |1.7-4 |2025-08-28 |CRAN (R 4.5.0) | |memoise |2.0.1 |2021-11-26 |CRAN (R 4.5.0) | -|mgcv |1.9-4 |2025-11-07 |CRAN (R 4.5.0) | |mime |0.13 |2025-03-17 |CRAN (R 4.5.0) | |minqa |1.2.8 |2024-08-17 |CRAN (R 4.5.0) | |mvtnorm |1.3-2 |2024-11-04 |CRAN (R 4.5.2) | @@ -141,7 +136,6 @@ |openssl |2.3.5 |2026-02-26 |CRAN (R 4.5.2) | |openxlsx2 |1.25 |2026-03-07 |CRAN (R 4.5.2) | |otel |0.2.0 |2025-08-29 |CRAN (R 4.5.0) | -|pak |0.9.2 |2025-12-22 |CRAN (R 4.5.2) | |parameters |0.28.3 |2025-11-25 |CRAN (R 4.5.2) | |patchwork |1.3.2 |2025-08-25 |CRAN (R 4.5.0) | |pbmcapply |1.5.1 |2022-04-28 |CRAN (R 4.5.0) | @@ -153,7 +147,6 @@ |pkgload |1.5.0 |2026-02-03 |CRAN (R 4.5.2) | |plyr |1.8.9 |2023-10-02 |CRAN (R 4.5.0) | |polyclip |1.10-7 |2024-07-23 |CRAN (R 4.5.0) | -|polylabelr |1.0.0 |2026-01-19 |CRAN (R 4.5.2) | |pracma |2.4.6 |2025-10-22 |CRAN (R 4.5.0) | |processx |3.8.6 |2025-02-21 |CRAN (R 4.5.0) | |promises |1.5.0 |2025-11-01 |CRAN (R 4.5.0) | @@ -198,7 +191,6 @@ |sessioninfo |1.2.3 |2025-02-05 |CRAN (R 4.5.0) | |shiny |1.13.0 |2026-02-20 |CRAN (R 4.5.2) | |shiny.i18n |0.3.0 |2023-01-16 |CRAN (R 4.5.0) | -|shiny2docker |0.0.3 |2025-06-28 |CRAN (R 4.5.0) | |shinybusy |0.3.3 |2024-03-09 |CRAN (R 4.5.0) | |shinyjs |2.1.1 |2026-01-15 |CRAN (R 4.5.2) | |shinyTime |1.0.3 |2022-08-19 |CRAN (R 4.5.0) | @@ -231,5 +223,4 @@ |xml2 |1.5.2 |2026-01-17 |CRAN (R 4.5.2) | |xtable |1.8-8 |2026-02-22 |CRAN (R 4.5.2) | |yaml |2.3.12 |2025-12-10 |CRAN (R 4.5.2) | -|yesno |0.1.3 |2024-07-26 |CRAN (R 4.5.0) | |zip |2.3.3 |2025-05-13 |CRAN (R 4.5.0) | diff --git a/app_docker/app.R b/app_docker/app.R index 9eb30b87..4dd38592 100644 --- a/app_docker/app.R +++ b/app_docker/app.R @@ -1,7 +1,7 @@ ######## -#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//RtmpAe8F1F/file150d92b07c28b.R +#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//RtmprUCGcI/file4761ae70bf7.R ######## i18n_path <- here::here("translations") @@ -64,7 +64,7 @@ i18n$set_translation_language("en") #### Current file: /Users/au301842/FreesearchR/R//app_version.R ######## -app_version <- function()'26.6.1' +app_version <- function()'26.4.2' ######## @@ -2151,23 +2151,12 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { list( bslib::layout_sidebar( sidebar = bslib::sidebar( - shiny::actionButton( - inputId = ns("act_plot"), - label = i18n$t("Plot"), - width = "100%", - icon = phosphoricons::ph("paint-brush", weight = "bold"), - # icon = shiny::icon("palette"), - disabled = FALSE - ), - shiny::helpText( - i18n$t('Adjust plot input and settings below, then press "Plot".') - ), bslib::accordion( id = "acc_plot", multiple = FALSE, bslib::accordion_panel( value = "acc_pan_plot", - title = i18n$t("Define plot"), + title = "Create plot", icon = phosphoricons::ph("chart-line"), # icon = bsicons::bs_icon("graph-up"), shiny::uiOutput(outputId = ns("primary")), @@ -2178,16 +2167,19 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { ), shiny::tags$br(), shiny::uiOutput(outputId = ns("type")), - shiny::h5(i18n$t("Other variables")), shiny::uiOutput(outputId = ns("secondary")), - shiny::uiOutput(outputId = ns("tertiary")) - ), - bslib::accordion_panel( - value = "acc_pan_params", - title = i18n$t("Settings"), - icon = phosphoricons::ph("gear"), + shiny::uiOutput(outputId = ns("tertiary")), shiny::uiOutput(outputId = ns("color_palette")), - shiny::uiOutput(outputId = ns("basic_parameters")), + shiny::br(), + shiny::actionButton( + inputId = ns("act_plot"), + label = i18n$t("Plot"), + width = "100%", + icon = phosphoricons::ph("paint-brush",weight = "bold"), + # icon = shiny::icon("palette"), + disabled = FALSE + ), + shiny::helpText(i18n$t('Adjust settings, then press "Plot".')) ), bslib::accordion_panel( value = "acc_pan_download", @@ -2240,14 +2232,14 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { shiny::p( "We have collected a few notes on visualising data and details on the options included in FreesearchR:", shiny::tags$a( - href = "https://freesearchr.github.io/FreesearchR-knowledge/app/visuals.html", + href = "https://agdamsbo.github.io/FreesearchR/articles/visuals.html", "View notes in new tab", target = "_blank", rel = "noopener noreferrer" ) ) ), - shiny::plotOutput(ns("plot"), height = "65vh"), + shiny::plotOutput(ns("plot"), height = "70vh"), shiny::tags$br(), shiny::tags$br(), shiny::htmlOutput(outputId = ns("code_plot")) @@ -2264,7 +2256,10 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { #' @name data-plots #' @returns shiny server module #' @export -data_visuals_server <- function(id, data, palettes = color_choices(), ...) { +data_visuals_server <- function(id, + data, + palettes, + ...) { shiny::moduleServer( id = id, module = function(input, output, session) { @@ -2316,99 +2311,69 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { plot_data <- data()[input$primary] } - plots <- possible_plots(data = plot_data, source_list = available_plots()) + plots <- possible_plots(data = plot_data) - plots_named <- get_input_params(plots) |> + plots_named <- get_plot_options(plots) |> lapply(\(.x) { stats::setNames(.x$descr, .x$note) }) - # plots_named <- get_plot_options(plots) |> - # lapply(\(.x) { - # stats::setNames(.x$descr, .x$note) - # }) - vectorSelectInput( inputId = ns("type"), selected = NULL, - label = shiny::h5(i18n$t("Plot type")), + label = shiny::h4(i18n$t("Plot type")), choices = Reduce(c, plots_named), multiple = FALSE ) }) rv$plot.params <- shiny::reactive({ - get_input_params(input$type) |> purrr::pluck(1) - # get_plot_options(input$type) |> purrr::pluck(1) + get_plot_options(input$type) |> purrr::pluck(1) }) - - ### Include two additional variable inputs output$secondary <- shiny::renderUI({ shiny::req(input$type) - # Get the plot function name - base_params <- rv$plot.params()[["base"]] + cols <- c(rv$plot.params()[["secondary.extra"]], all_but(colnames( + subset_types(data(), rv$plot.params()[["secondary.type"]]) + ), input$primary)) - filtered_params <- base_params[sapply(base_params, function(params) { - params$id %in% "secondary" - })][[1]] - - filtered_params$exclude <- input$primary - - create_input_element( - input_id = "secondary", - ns = ns, - params = append_list(data(), filtered_params, "data") + columnSelectInput( + inputId = ns("secondary"), + data = data, + selected = cols[1], + placeholder = i18n$t("Please select"), + label = if (isTRUE(rv$plot.params()[["secondary.multi"]])) + i18n$t("Additional variables") + else + i18n$t("Secondary variable"), + multiple = rv$plot.params()[["secondary.multi"]], + maxItems = rv$plot.params()[["secondary.max"]], + col_subset = cols, + none_label = i18n$t("No variable") ) - }) output$tertiary <- shiny::renderUI({ shiny::req(input$type) - # Get the plot function name - base_params <- rv$plot.params()[["base"]] - - filtered_params <- base_params[sapply(base_params, function(params) { - params$id %in% "tertiary" - })][[1]] - - filtered_params$exclude <- c(input$primary, input$secondary) - - create_input_element( - input_id = "tertiary", - ns = ns, - params = append_list(data(), filtered_params, "data") + columnSelectInput( + inputId = ns("tertiary"), + data = data, + placeholder = i18n$t("Please select"), + label = i18n$t("Grouping variable"), + multiple = FALSE, + col_subset = c( + "none", + all_but( + colnames(subset_types(data(), rv$plot.params()[["tertiary.type"]])), + input$primary, + input$secondary + ) + ), + none_label = i18n$t("No stratification") ) }) - - ### Generating additional parameter inputs if any specified - output$basic_parameters <- renderUI({ - req(input$type, rv$plot.params) - - # Get the plot function name - base_params <- rv$plot.params()[["base"]] - - filtered_params <- base_params[sapply(base_params, function(params) { - !params$id %in% c("secondary", "tertiary") - })] - - - # Create UI elements for base parameters - base_inputs <- lapply(filtered_params, function(params) { - input_id <- paste0("base_", params$id) - params$id <- NULL - if (params$type %in% "select_variables") { - params$data <- data() - } - - create_input_element(params, ns, input_id) - }) - tagList(base_inputs) - - }) - ### Color option output$color_palette <- shiny::renderUI({ # shiny::req(input$type) @@ -2422,49 +2387,19 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { shiny::observeEvent(input$act_plot, { if (NROW(data()) > 0) { - tryCatch({ - # Get all input values with prefixes - base_inputs <- reactiveValuesToList(input)[grep("^base_", names(reactiveValuesToList(input)))] - # advanced_inputs <- reactiveValuesToList(input)[grep("^advanced_", names(reactiveValuesToList(input)))] - - # Remove the prefix from names - names(base_inputs) <- gsub("^base_", "", names(base_inputs)) - # names(advanced_inputs) <- gsub("^advanced_", "", names(advanced_inputs)) - - base_inputs <- c(base_inputs, - list(color.palette = input$color_palette)) - - # If any of the specified parameters are NULL/missing, the settings - # accordion/panel was never opened, and they can be ignored, as - # default settings will the be used. - if (any(sapply(base_inputs, is.null))) { - dynamic_params <- list() - } else { - dynamic_params <- base_inputs - } - - # Build parameters for plotting function + tryCatch({ parameters <- list( type = rv$plot.params()[["fun"]], pri = input$primary, sec = input$secondary, - ter = input$tertiary + ter = input$tertiary, + color.palette = input$color_palette ) - parameters <- modifyList(parameters, dynamic_params) - ## If the dictionary holds additional arguments to pass to the ## plotting function, these are included if (!is.null(rv$plot.params()[["fun.args"]])) { - default_params <- rv$plot.params()[["fun.args"]] - - ## Ensure not to overwrite user defined parameters are overwritten - ## This allows to define default parameters. - ## - ## This will create a strange edge case, where the plot looks in - ## one way, when plotted initially, but may change, when the settings - ## accordion is opened. Problem for future me. Really mostly an edge case. - parameters <- modifyList(parameters, default_params[!names(default_params) %in% names(parameters)]) + parameters <- modifyList(parameters, rv$plot.params()[["fun.args"]]) } shiny::withProgress(message = i18n$t("Drawing the plot. Hold tight for a moment.."), @@ -2500,25 +2435,7 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { if (!is.null(rv$plot)) { rv$plot } else { - # Create a placeholder plot with instructions using ggplot2 - ggplot2::ggplot() + - ggplot2::annotate( - "text", - x = 0.5, - y = 0.5, - label = i18n$t("Select variables and plot type,\nthen click 'Plot' to generate visualization"), - size = 5, - color = "gray50", - lineheight = 0.8 - ) + - ggplot2::xlim(0, 1) + - ggplot2::ylim(0, 1) + - ggplot2::theme_void() + - ggplot2::theme( - panel.background = ggplot2::element_rect(fill = "white"), - plot.background = ggplot2::element_rect(fill = "white") - ) - # return(NULL) + return(NULL) } }) @@ -2563,6 +2480,506 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { ) } +#' Select all from vector but +#' +#' @param data vector +#' @param ... exclude +#' +#' @returns vector +#' @export +#' +#' @examples +#' all_but(1:10, c(2, 3), 11, 5) +all_but <- function(data, ...) { + data[!data %in% c(...)] +} + +#' Easily subset by data type function +#' +#' @param data data +#' @param types desired types +#' @param type.fun function to get type. Default is outcome_type +#' +#' @returns vector +#' @export +#' +#' @examples +#' default_parsing(mtcars) |> subset_types("ordinal") +#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical")) +#' #' default_parsing(mtcars) |> subset_types("factor",class) +subset_types <- function(data, types, type.fun = data_type) { + data[sapply(data, type.fun) %in% types] +} + + +#' Implemented functions +#' +#' @description +#' Library of supported functions. The list name and "descr" element should be +#' unique for each element on list. +#' +#' - descr: Plot description +#' +#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal) +#' +#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal) +#' +#' - secondary.extra: "none" or NULL to have option to choose none. +#' +#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal) +#' +#' +#' @returns list +#' @export +#' +#' @examples +#' supported_plots() |> str() +supported_plots <- function() { + list( + plot_bar_rel = list( + fun = "plot_bar", + fun.args = list(style = "fill"), + descr = i18n$t("Stacked relative barplot"), + note = i18n$t( + "Create relative stacked barplots to show the distribution of categorical levels" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ), + plot_bar_abs = list( + fun = "plot_bar", + fun.args = list(style = "dodge"), + descr = i18n$t("Side-by-side barplot"), + note = i18n$t( + "Create side-by-side barplot to show the distribution of categorical levels" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_hbars = list( + fun = "plot_hbars", + descr = i18n$t("Stacked horizontal bars"), + note = i18n$t( + "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_violin = list( + fun = "plot_violin", + descr = i18n$t("Violin plot"), + note = i18n$t( + "A modern alternative to the classic boxplot to visualise data distribution" + ), + primary.type = c("datatime", "continuous"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + secondary.extra = "none", + tertiary.type = c("dichotomous", "categorical") + ), + # plot_ridge = list( + # descr = "Ridge plot", + # note = "An alternative option to visualise data distribution", + # primary.type = "continuous", + # secondary.type = c("dichotomous" ,"categorical"), + # tertiary.type = c("dichotomous" ,"categorical"), + # secondary.extra = NULL + # ), + plot_sankey = list( + fun = "plot_sankey", + descr = i18n$t("Sankey plot"), + note = i18n$t("A way of visualising change between groups"), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + secondary.extra = NULL, + tertiary.type = c("dichotomous", "categorical") + ), + plot_scatter = list( + fun = "plot_scatter", + descr = i18n$t("Scatter plot"), + note = i18n$t("A classic way of showing the association between to variables"), + primary.type = c("datatime", "continuous"), + secondary.type = c("datatime", "continuous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ), + plot_box = list( + fun = "plot_box", + descr = i18n$t("Box plot"), + note = i18n$t("A classic way to plot data distribution by groups"), + primary.type = c("datatime", "continuous"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_euler = list( + fun = "plot_euler", + descr = i18n$t("Euler diagram"), + note = i18n$t( + "Generate area-proportional Euler diagrams to display set relationships" + ), + primary.type = c("dichotomous"), + secondary.type = c("dichotomous"), + secondary.multi = TRUE, + secondary.max = 4, + tertiary.type = c("dichotomous"), + secondary.extra = NULL + ), + plot_euler = list( + fun = "plot_likert", + descr = i18n$t("Likert diagram"), + note = i18n$t( + "Plot survey results" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = TRUE, + secondary.extra = NULL, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ) + ) +} + +#' Get possible regression models +#' +#' @param data data +#' +#' @returns character vector +#' @export +#' +#' @examples +#' mtcars |> +#' default_parsing() |> +#' dplyr::pull("cyl") |> +#' possible_plots() +#' +#' mtcars |> +#' default_parsing() |> +#' dplyr::select("mpg") |> +#' possible_plots() +possible_plots <- function(data) { + # browser() + # data <- if (is.reactive(data)) data() else data + if (is.data.frame(data)) { + data <- data[[1]] + } + + type <- data_type(data) + + if (type == "unknown") { + out <- type + } else { + out <- supported_plots() |> + lapply(\(.x) { + if (type %in% .x$primary.type) { + .x$descr + } + }) |> + unlist() + } + unname(out) +} + +#' Get the function options based on the selected function description +#' +#' @param data vector +#' +#' @returns list +#' @export +#' +#' @examples +#' ls <- mtcars |> +#' default_parsing() |> +#' dplyr::pull(mpg) |> +#' possible_plots() |> +#' (\(.x){ +#' .x[[1]] +#' })() |> +#' get_plot_options() +get_plot_options <- function(data) { + descrs <- supported_plots() |> + lapply(\(.x) { + .x$descr + }) |> + unlist() + supported_plots() |> + (\(.x) { + .x[match(data, descrs)] + })() +} + + + +#' Wrapper to create plot based on provided type +#' +#' @param data data.frame +#' @param pri primary variable +#' @param sec secondary variable +#' @param ter tertiary variable +#' @param type plot type (derived from possible_plots() and matches custom function) +#' @param color.palette choose color palette. See \code{\link{plot_colors}} for support. +#' @param ... ignored for now +#' +#' @name data-plots +#' +#' @returns ggplot2 object +#' @export +#' +#' @examples +#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes() +create_plot <- function(data, + type, + pri, + sec, + ter = NULL, + color.palette = "viridis", + ...) { + if (!is.null(sec)) { + if (!any(sec %in% names(data))) { + sec <- NULL + } + } + + if (!is.null(ter)) { + if (!ter %in% names(data)) { + ter <- NULL + } + } + + parameters <- list( + pri = pri, + sec = sec, + ter = ter, + color.palette = color.palette, + ... + ) + + out <- do.call(type, modifyList(parameters, list(data = data))) + + code <- rlang::call2(type, !!!parameters, .ns = "FreesearchR") + + attr(out, "code") <- code + out +} + +#' Print label, and if missing print variable name for plots +#' +#' @param data vector or data frame +#' @param var variable name. Optional. +#' +#' @returns character string +#' @export +#' +#' @examples +#' mtcars |> get_label(var = "mpg") +#' mtcars |> get_label() +#' mtcars$mpg |> get_label() +#' gtsummary::trial |> get_label(var = "trt") +#' gtsummary::trial$trt |> get_label() +#' 1:10 |> get_label() +get_label <- function(data, var = NULL) { + # data <- if (is.reactive(data)) data() else data + if (!is.null(var) & is.data.frame(data)) { + data <- data[[var]] + } + out <- REDCapCAST::get_attr(data = data, attr = "label") + if (is.na(out)) { + if (is.null(var)) { + out <- deparse(substitute(data)) + } else { + if (is.symbol(var)) { + out <- gsub('\"', "", deparse(substitute(var))) + } else { + out <- var + } + } + } + out +} + + +#' Line breaking at given number of characters for nicely plotting labels +#' +#' @param data string +#' @param lineLength maximum line length +#' @param fixed flag to force split at exactly the value given in lineLength. +#' Default is FALSE, only splitting at spaces. +#' +#' @returns character string +#' @export +#' +#' @examples +#' "Lorem ipsum... you know the routine" |> line_break() +#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE) +line_break <- function(data, + lineLength = 20, + force = FALSE) { + if (isTRUE(force)) { + ## This eats some letters when splitting a sentence... ?? + gsub(paste0("(.{1,", lineLength, "})(\\s|[[:alnum:]])"), + "\\1\n", + data) + } else { + paste(strwrap(data, lineLength), collapse = "\n") + } + ## https://stackoverflow.com/a/29847221 +} + + +#' Wrapping +#' +#' @param data list of ggplot2 objects +#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL +#' @param title panel title +#' @param guides passed to patchwork::wrap_plots() +#' @param axes passed to patchwork::wrap_plots() +#' @param axis_titles passed to patchwork::wrap_plots() +#' @param ... passed to patchwork::wrap_plots() +#' +#' @returns list of ggplot2 objects +#' @export +#' +wrap_plot_list <- function(data, + tag_levels = NULL, + title = NULL, + axis.font.family = NULL, + guides = "collect", + axes = "collect", + axis_titles = "collect", + y.axis.percentage = FALSE, + ...) { + if (ggplot2::is_ggplot(data[[1]])) { + if (length(data) > 1) { + out <- data |> + (\(.x) { + if (rlang::is_named(.x)) { + purrr::imap(.x, \(.y, .i) { + .y + ggplot2::ggtitle(.i) + }) + } else { + .x + } + })() |> + align_axes(percentage=y.axis.percentage) |> + patchwork::wrap_plots(guides = guides, + axes = axes, + axis_titles = axis_titles, + ...) + if (!is.null(tag_levels)) { + out <- out + patchwork::plot_annotation(tag_levels = tag_levels) + } + if (!is.null(title)) { + out <- out + + patchwork::plot_annotation( + title = title, + theme = ggplot2::theme(plot.title = ggplot2::element_text(size = 25)) + ) + } + } else { + out <- data[[1]] + } + } else { + cli::cli_abort("Can only wrap lists of {.cls ggplot} objects") + } + + if (!is.null(axis.font.family)) { + if (inherits(x = out, what = "patchwork")) { + out <- out & + ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) + } else { + out <- out + + ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) + } + } + + out +} + + +#' Aligns axes between plots +#' +#' @param ... ggplot2 objects or list of ggplot2 objects +#' +#' @returns list of ggplot2 objects +#' @export +#' +align_axes <- function(..., + x.axis = TRUE, + y.axis = TRUE, + percentage = FALSE) { + # https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object + # https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150 + if (ggplot2::is_ggplot(..1)) { + ## Assumes list of ggplots + p <- list(...) + } else if (is.list(..1)) { + ## Assumes list with list of ggplots + p <- ..1 + } else { + cli::cli_abort("Can only align {.cls ggplot} objects or a list of them") + } + + yr <- clean_common_axis(p, "y") + + xr <- clean_common_axis(p, "x") + + suppressWarnings({ + p_out <- purrr::map(p, \(.x) { + out <- .x + if (isTRUE(x.axis)) { + out <- out + ggplot2::xlim(xr) + } + if (isTRUE(y.axis)) { + out <- out + ggplot2::ylim(yr) + } + out + }) + }) + + if(isTRUE(percentage)){ + lapply(p_out,\(.x){ + .x+ + ggplot2::scale_y_continuous(labels = scales::percent) + }) + } else { + p_out + } +} + +#' Extract and clean axis ranges +#' +#' @param p plot +#' @param axis axis. x or y. +#' +#' @returns vector +#' @export +#' +clean_common_axis <- function(p, axis) { + purrr::map(p, ~ ggplot2::layer_scales(.x)[[axis]]$get_limits()) |> + unlist() |> + (\(.x) { + if (is.numeric(.x)) { + range(.x) + } else { + as.character(.x) + } + })() |> + unique() +} + ######## #### Current file: /Users/au301842/FreesearchR/R//data-summary.R @@ -3428,25 +3845,38 @@ footer_ui <- function(i18n) { #' #' @export generate_colors <- function(n, palette = "viridis", ...) { - - # --- Input validation ------------------------------------------------------- - if (!is.numeric(n) || length(n) != 1 || n < 1 || n %% 1 != 0) { + if (!is.numeric(n) || + length(n) != 1 || n < 1 || n != as.integer(n)) { stop("`n` must be a single positive integer.") } - if (!is.function(palette) && (!is.character(palette) || length(palette) != 1)) { - stop("`palette` must be a single character string or a function.") - } - # --- Function passthrough --------------------------------------------------- + # Function passthrough — call directly with n and ... if (is.function(palette)) { return(palette(n, ...)) } - # --- Named palette dispatch ------------------------------------------------- + if (!is.character(palette) || length(palette) != 1) { + stop("`palette` must be a single character string or a function.") + } + + if (!is.numeric(n) || + length(n) != 1 || n < 1 || n != as.integer(n)) { + stop("`n` must be a single positive integer.") + } + if (!is.character(palette) || length(palette) != 1) { + stop("`palette` must be a single character string.") + } + palette_lower <- tolower(palette) - viridis_palettes <- c("viridis", "magma", "plasma", "inferno", - "cividis", "mako", "rocket", "turbo") + viridis_palettes <- c("viridis", + "magma", + "plasma", + "inferno", + "cividis", + "mako", + "rocket", + "turbo") if (palette_lower %in% viridis_palettes) { viridisLite::viridis(n = n, option = palette_lower, ...) @@ -3466,42 +3896,35 @@ generate_colors <- function(n, palette = "viridis", ...) { } else if (palette_lower == "topo") { grDevices::topo.colors(n = n, ...) + } else if (palette %in% rownames(RColorBrewer::brewer.pal.info)) { + max_n <- RColorBrewer::brewer.pal.info[palette, "maxcolors"] + fetch_n <- max(min(n, max_n), 3L) # clamp to [3, max_n] for brewer.pal() + base_colors <- RColorBrewer::brewer.pal(n = fetch_n, name = palette) + grDevices::colorRampPalette(base_colors)(n) + + } else if (palette %in% grDevices::palette.pals()) { + grDevices::colorRampPalette(palette.colors(palette = palette))(n) + + } else if (palette %in% grDevices::hcl.pals()) { + grDevices::hcl.colors(n = n, palette = palette, ...) + } else { - # Case-insensitive RColorBrewer lookup - brewer_names <- rownames(RColorBrewer::brewer.pal.info) - brewer_match <- brewer_names[match(palette_lower, tolower(brewer_names))] - - if (!is.na(brewer_match)) { - max_n <- RColorBrewer::brewer.pal.info[brewer_match, "maxcolors"] - fetch_n <- max(min(n, max_n), 3L) - base_colors <- RColorBrewer::brewer.pal(n = fetch_n, name = brewer_match) - grDevices::colorRampPalette(base_colors)(n) - - } else { - # Case-insensitive grDevices palette.pals() lookup - pal_names <- grDevices::palette.pals() - pal_match <- pal_names[match(palette_lower, tolower(pal_names))] - - if (!is.na(pal_match)) { - grDevices::colorRampPalette(grDevices::palette.colors(palette = pal_match))(n) - - } else if (palette %in% grDevices::hcl.pals()) { - # Named HCL palettes (e.g. "Rocket", "Plasma") — distinct from viridisLite - grDevices::hcl.colors(n = n, palette = palette, ...) - - } else { - warning( - "Unknown palette: '", palette, "'. Falling back to viridis.\n", - "Available options:\n", - " viridisLite : viridis, magma, plasma, inferno, cividis, mako, rocket, turbo\n", - " grDevices : hcl, rainbow, heat, terrain, topo\n", - " grDevices HCL: use grDevices::hcl.pals() to see all options\n", - " grDevices : use grDevices::palette.pals() to see all options\n", - " RColorBrewer : use RColorBrewer::brewer.pal.info to see all options" - ) - viridisLite::viridis(n = n, option = "viridis") - } - } + message( + paste0( + "Unknown palette: '", + palette, + "'. ", + "Falling back to default R colors.\n", + "Available options:\n", + " viridisLite : viridis, magma, plasma, inferno, cividis, mako, rocket, turbo\n", + " grDevices : hcl, rainbow, heat, terrain, topo\n", + " grDevices HCL: use grDevices::hcl.pals() to see all options\n", + " grDevices : use grDevices::palette.pals() to see all options\n", + " RColorBrewer : use RColorBrewer::brewer.pal.info to see all options" + ) + ) + viridisLite::viridis(n = n, option = "viridis") + # grDevices::hcl.colors(n = n) } } @@ -4534,7 +4957,7 @@ apply_idea_filter <- function(filtered_reactive, df_target, env = parent.frame() #### Current file: /Users/au301842/FreesearchR/R//hosted_version.R ######## -hosted_version <- function()'v26.6.1' +hosted_version <- function()'v26.4.2-260410' ######## @@ -6548,14 +6971,14 @@ plot_bar <- function(data, sec = sec, style = style, max_level = max_level, - color.palette = color.palette, - ... + color.palette = color.palette ) }) wrap_plot_list(out, title = glue::glue(i18n$t("Grouped by {get_label(data,ter)}")), - y.axis.percentage = TRUE) + y.axis.percentage = TRUE, + ...) } @@ -6676,11 +7099,11 @@ plot_box <- function(data, pri, sec, ter = NULL,color.palette="viridis",...) { data = .ds, pri = pri, sec = sec, - color.palette=color.palette, ... + color.palette=color.palette ) }) - wrap_plot_list(out,title=glue::glue(i18n$t("Grouped by {get_label(data,ter)}"))) + wrap_plot_list(out,title=glue::glue(i18n$t("Grouped by {get_label(data,ter)}")),...) } @@ -6874,7 +7297,7 @@ plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103,color.palette="vi #' D = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE) #' ) |> plot_euler_single() #' mtcars[c("vs", "am")] |> plot_euler_single("magma") -plot_euler_single <- function(data,color.palette="viridis", ...) { +plot_euler_single <- function(data,color.palette="viridis") { data |> ggeulerr(shape = "circle") + @@ -6917,15 +7340,13 @@ plot_hbars <- function(data, pri, sec, ter = NULL, - color.palette = "viridis", - ...) { + color.palette = "viridis") { vertical_stacked_bars( data = data, score = pri, group = sec, strata = ter, - color.palette = color.palette, - ... + color.palette = color.palette ) } @@ -6978,7 +7399,7 @@ vertical_stacked_bars <- function(data, colors <- generate_colors(n = nrow(df.table), palette = color.palette) ## Colors are reversed by default as that usually gives the best result - if (isTRUE(reverse) | reverse=="TRUE") { + if (isTRUE(reverse)) { colors <- rev(colors) } @@ -7035,8 +7456,7 @@ plot_likert <- function(data, pri, sec = NULL, ter = NULL, - color.palette = "viridis", - ...) { + color.palette = "viridis") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -7213,8 +7633,7 @@ plot_sankey <- function(data, default.color = "#2986cc", box.color = "#1E4B66", na.color = "grey80", - missing.level = "Missing", - ...) { + missing.level = "Missing") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -7448,7 +7867,7 @@ color_levels_gen <- function(data,na.color="grey80",palette="viridis"){ #' @examples #' mtcars |> plot_scatter(pri = "mpg", sec = "wt") #' mtcars |> plot_scatter(pri = "mpg", sec = "wt",ter="carb") -plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis", ...) { +plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis") { if (is.null(ter)) { rempsyc::nice_scatter( data = data, @@ -7485,7 +7904,7 @@ plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis", .. #' @examples #' mtcars |> plot_violin(pri = "mpg", sec = "cyl") #' mtcars |> plot_violin(pri = "mpg", sec = "cyl", ter = "gear", color.palette="Blues") -plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis", ...) { +plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -7500,8 +7919,7 @@ plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis", ... group = sec, response = pri, xtitle = get_label(data, var = sec), - ytitle = get_label(data, var = pri), - ... + ytitle = get_label(data, var = pri) )+ scale_fill_generate(palette=color.palette) }) @@ -7647,890 +8065,6 @@ plot_download_demo_app <- function() { # plot_download_demo_app() -######## -#### Current file: /Users/au301842/FreesearchR/R//plot-helpers.R -######## - -#' Implemented functions -#' -#' @description -#' Library of supported functions. The list name and "descr" element should be -#' unique for each element on list. -#' -#' - fun: the plotting function -#' -#' - fun.args: default parameters for the plotting function -#' -#' - descr: Plot description -#' -#' - note: Short note/description of the function for displaying in ui and docs -#' -#' - primary.type: Primary variable data type (see [data_type]) -#' -#' - base: holds a list of parameters for plot input fields generation -#' Secondary and tertiary variable input fields are mandatory. -#' -#' -#' @returns list -#' @export -#' -#' @examples -#' available_plots() |> str() -available_plots <- function() { - list( - plot_bar_rel = list( - fun = "plot_bar", - fun.args = list(style = "fill"), - descr = i18n$t("Stacked relative barplot"), - note = i18n$t( - "Create relative stacked barplots to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Additional variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_bar_abs = list( - fun = "plot_bar", - fun.args = list(style = "dodge"), - descr = i18n$t("Side-by-side barplot"), - note = i18n$t( - "Create side-by-side barplot to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_hbars = list( - fun = "plot_hbars", - descr = i18n$t("Stacked horizontal bars"), - note = i18n$t( - "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ), - list( - id = "reverse", - type = "select_input", - label = i18n$t("Reverse colors"), - choices = c(yes = TRUE, no = FALSE) - ) - ), - advanced = list() - ######### - ), - plot_violin = list( - fun = "plot_violin", - descr = i18n$t("Violin plot"), - note = i18n$t( - "A modern alternative to the classic boxplot to visualise data distribution" - ), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_sankey = list( - fun = "plot_sankey", - descr = i18n$t("Sankey plot"), - note = i18n$t("A way of visualising change between groups"), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_scatter = list( - fun = "plot_scatter", - descr = i18n$t("Scatter plot"), - note = i18n$t("A classic way of showing the association between to variables"), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("datatime", "continuous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_box = list( - fun = "plot_box", - descr = i18n$t("Box plot"), - note = i18n$t("A classic way to plot data distribution by groups"), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_euler = list( - fun = "plot_euler", - descr = i18n$t("Euler diagram"), - note = i18n$t( - "Generate area-proportional Euler diagrams to display set relationships" - ), - primary.type = c("dichotomous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = TRUE, - maxItems = 4 - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_likert = list( - fun = "plot_likert", - descr = i18n$t("Likert diagram"), - note = i18n$t("Plot survey results"), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Additional variables"), - multiple = TRUE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ) - ) -} - -# Helper function to create input elements dynamically -create_input_element <- function(params, ns, input_id) { - # Add the namespaced inputId to the arguments - params$inputId <- ns(input_id) - - # Map input types to Shiny functions - input_function <- switch( - params$type, - "numeric_input" = shiny::numericInput, - "select_input" = shiny::selectInput, - "checkbox_input" = shiny::checkboxInput, - "slider_input" = shiny::sliderInput, - "text_input" = shiny::textInput, - "select_variables" = selectPlotVariables - ) - - params$type <- NULL - params$id <- NULL - - - # Call the function with all arguments - do.call(input_function, params) -} - -#' Wrapper for columnSelectInput -#' -selectPlotVariables <- function(data, - exclude = NULL, - allow_none = TRUE, - var_types, - ...) { - datar <- if (is.reactive(data)) { - data - } else { - reactive(data) - } - - cols <- all_but(colnames(subset_types(datar(), var_types)), exclude) - - if (isTRUE(allow_none)) { - cols <- c("none", cols) - } - - params <- list(...) - - params$none_label <- i18n$t("No variable") - params$col_subset <- cols - - rlang::exec(columnSelectInput, !!!append_list(datar(), params, "data")) -} - - - -#' Select all from vector but -#' -#' @param data vector -#' @param ... exclude -#' -#' @returns vector -#' @export -#' -#' @examples -#' all_but(1:10, c(2, 3), 11, 5) -all_but <- function(data, ...) { - data[!data %in% c(...)] -} - -#' Easily subset by data type function -#' -#' @param data data -#' @param types desired types -#' @param type.fun function to get type. Default is outcome_type -#' -#' @returns vector -#' @export -#' -#' @examples -#' default_parsing(mtcars) |> subset_types("ordinal") -#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical")) -#' #' default_parsing(mtcars) |> subset_types("factor",class) -subset_types <- function(data, types, type.fun = data_type) { - data[sapply(data, type.fun) %in% types] -} - - -#' Implemented functions -#' -#' @description -#' Library of supported functions. The list name and "descr" element should be -#' unique for each element on list. -#' -#' - descr: Plot description -#' -#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal) -#' -#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal) -#' -#' - secondary.extra: "none" or NULL to have option to choose none. -#' -#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal) -#' -#' -#' @returns list -#' @export -#' -#' @examples -#' supported_plots() |> str() -supported_plots <- function() { - list( - plot_bar_rel = list( - fun = "plot_bar", - fun.args = list(style = "fill"), - descr = i18n$t("Stacked relative barplot"), - note = i18n$t( - "Create relative stacked barplots to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ), - plot_bar_abs = list( - fun = "plot_bar", - fun.args = list(style = "dodge"), - descr = i18n$t("Side-by-side barplot"), - note = i18n$t( - "Create side-by-side barplot to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_hbars = list( - fun = "plot_hbars", - descr = i18n$t("Stacked horizontal bars"), - note = i18n$t( - "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_violin = list( - fun = "plot_violin", - descr = i18n$t("Violin plot"), - note = i18n$t( - "A modern alternative to the classic boxplot to visualise data distribution" - ), - primary.type = c("datatime", "continuous"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - secondary.extra = "none", - tertiary.type = c("dichotomous", "categorical") - ), - # plot_ridge = list( - # descr = "Ridge plot", - # note = "An alternative option to visualise data distribution", - # primary.type = "continuous", - # secondary.type = c("dichotomous" ,"categorical"), - # tertiary.type = c("dichotomous" ,"categorical"), - # secondary.extra = NULL - # ), - plot_sankey = list( - fun = "plot_sankey", - descr = i18n$t("Sankey plot"), - note = i18n$t("A way of visualising change between groups"), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - secondary.extra = NULL, - tertiary.type = c("dichotomous", "categorical") - ), - plot_scatter = list( - fun = "plot_scatter", - descr = i18n$t("Scatter plot"), - note = i18n$t("A classic way of showing the association between to variables"), - primary.type = c("datatime", "continuous"), - secondary.type = c("datatime", "continuous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ), - plot_box = list( - fun = "plot_box", - descr = i18n$t("Box plot"), - note = i18n$t("A classic way to plot data distribution by groups"), - primary.type = c("datatime", "continuous"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_euler = list( - fun = "plot_euler", - descr = i18n$t("Euler diagram"), - note = i18n$t( - "Generate area-proportional Euler diagrams to display set relationships" - ), - primary.type = c("dichotomous"), - secondary.type = c("dichotomous"), - secondary.multi = TRUE, - secondary.max = 4, - tertiary.type = c("dichotomous"), - secondary.extra = NULL - ), - plot_likert = list( - fun = "plot_likert", - descr = i18n$t("Likert diagram"), - note = i18n$t("Plot survey results"), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = TRUE, - secondary.extra = NULL, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ) - ) -} - -#' Get possible regression models -#' -#' @param data data -#' -#' @returns character vector -#' @export -#' -#' @examples -#' mtcars |> -#' default_parsing() |> -#' dplyr::pull("cyl") |> -#' possible_plots() -#' -#' mtcars |> -#' default_parsing() |> -#' dplyr::select("mpg") |> -#' possible_plots() -possible_plots <- function(data, source_list = supported_plots()) { - # browser() - # data <- if (is.reactive(data)) data() else data - if (is.data.frame(data)) { - data <- data[[1]] - } - - type <- data_type(data) - - if (type == "unknown") { - out <- type - } else { - out <- source_list |> - lapply(\(.x) { - if (type %in% .x$primary.type) { - .x$descr - } - }) |> - unlist() - } - unname(out) -} - -#' Get the function options based on the selected function description -#' -#' @param data vector -#' -#' @returns list -#' @export -#' -#' @examples -#' ls <- mtcars |> -#' default_parsing() |> -#' dplyr::pull(mpg) |> -#' possible_plots() |> -#' (\(.x){ -#' .x[[1]] -#' })() |> -#' get_plot_options() -get_plot_options <- function(data) { - descrs <- supported_plots() |> - lapply(\(.x) { - .x$descr - }) |> - unlist() - supported_plots() |> - (\(.x) { - .x[match(data, descrs)] - })() -} - -#' Get the function parameters based on the selected function description -#' -#' @param data vector -#' -#' @returns list -#' @export -#' -#' @examples -#' ls <- mtcars |> -#' default_parsing() |> -#' dplyr::pull(mpg) |> -#' possible_plots() |> -#' (\(.x){ -#' .x[[1]] -#' })() |> -#' get_input_params() -get_input_params <- function(data) { - descr <- available_plots() |> - lapply(\(.x) { - .x$descr - }) |> - unlist() - available_plots() |> - (\(.x) { - .x[match(data, descr)] - })() -} - - -#' Wrapper to create plot based on provided type -#' -#' @param data data.frame -#' @param pri primary variable -#' @param sec secondary variable -#' @param ter tertiary variable -#' @param type plot type (derived from possible_plots() and matches custom function) -#' @param color.palette choose color palette. See \code{\link{plot_colors}} for support. -#' @param ... ignored for now -#' -#' @name data-plots -#' -#' @returns ggplot2 object -#' @export -#' -#' @examples -#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes() -create_plot <- function(data, - type, - pri, - sec, - ter = NULL, - color.palette = "viridis", - ...) { - if (!is.null(sec)) { - if (!any(sec %in% names(data))) { - sec <- NULL - } - } - - if (!is.null(ter)) { - if (!ter %in% names(data)) { - ter <- NULL - } - } - - parameters <- list( - pri = pri, - sec = sec, - ter = ter, - color.palette = color.palette, - ... - ) - - out <- do.call(type, modifyList(parameters, list(data = data))) - - code <- rlang::call2(type, !!!parameters, .ns = "FreesearchR") - - attr(out, "code") <- code - out -} - -#' Print label, and if missing print variable name for plots -#' -#' @param data vector or data frame -#' @param var variable name. Optional. -#' -#' @returns character string -#' @export -#' -#' @examples -#' mtcars |> get_label(var = "mpg") -#' mtcars |> get_label() -#' mtcars$mpg |> get_label() -#' gtsummary::trial |> get_label(var = "trt") -#' gtsummary::trial$trt |> get_label() -#' 1:10 |> get_label() -get_label <- function(data, var = NULL) { - # data <- if (is.reactive(data)) data() else data - if (!is.null(var) & is.data.frame(data)) { - data <- data[[var]] - } - out <- REDCapCAST::get_attr(data = data, attr = "label") - if (is.na(out)) { - if (is.null(var)) { - out <- deparse(substitute(data)) - } else { - if (is.symbol(var)) { - out <- gsub('\"', "", deparse(substitute(var))) - } else { - out <- var - } - } - } - out -} - - -#' Line breaking at given number of characters for nicely plotting labels -#' -#' @param data string -#' @param lineLength maximum line length -#' @param fixed flag to force split at exactly the value given in lineLength. -#' Default is FALSE, only splitting at spaces. -#' -#' @returns character string -#' @export -#' -#' @examples -#' "Lorem ipsum... you know the routine" |> line_break() -#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE) -line_break <- function(data, - lineLength = 20, - force = FALSE) { - if (isTRUE(force)) { - ## This eats some letters when splitting a sentence... ?? - gsub(paste0("(.{1,", lineLength, "})(\\s|[[:alnum:]])"), - "\\1\n", - data) - } else { - paste(strwrap(data, lineLength), collapse = "\n") - } - ## https://stackoverflow.com/a/29847221 -} - - -#' Wrapping -#' -#' @param data list of ggplot2 objects -#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL -#' @param title panel title -#' @param guides passed to patchwork::wrap_plots() -#' @param axes passed to patchwork::wrap_plots() -#' @param axis_titles passed to patchwork::wrap_plots() -#' @param ... passed to patchwork::wrap_plots() -#' -#' @returns list of ggplot2 objects -#' @export -#' -wrap_plot_list <- function(data, - tag_levels = NULL, - title = NULL, - axis.font.family = NULL, - guides = "collect", - axes = "collect", - axis_titles = "collect", - y.axis.percentage = FALSE, - ...) { - if (ggplot2::is_ggplot(data[[1]])) { - if (length(data) > 1) { - out <- data |> - (\(.x) { - if (rlang::is_named(.x)) { - purrr::imap(.x, \(.y, .i) { - .y + ggplot2::ggtitle(.i) - }) - } else { - .x - } - })() |> - align_axes(percentage = y.axis.percentage) |> - patchwork::wrap_plots(guides = guides, - axes = axes, - axis_titles = axis_titles, - ...) - if (!is.null(tag_levels)) { - out <- out + patchwork::plot_annotation(tag_levels = tag_levels) - } - if (!is.null(title)) { - out <- out + - patchwork::plot_annotation( - title = title, - theme = ggplot2::theme(plot.title = ggplot2::element_text(size = 25)) - ) - } - } else { - out <- data[[1]] - } - } else { - cli::cli_abort("Can only wrap lists of {.cls ggplot} objects") - } - - if (!is.null(axis.font.family)) { - if (inherits(x = out, what = "patchwork")) { - out <- out & - ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) - } else { - out <- out + - ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) - } - } - - out -} - - -#' Aligns axes between plots -#' -#' @param ... ggplot2 objects or list of ggplot2 objects -#' -#' @returns list of ggplot2 objects -#' @export -#' -align_axes <- function(..., - x.axis = TRUE, - y.axis = TRUE, - percentage = FALSE) { - # https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object - # https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150 - if (ggplot2::is_ggplot(..1)) { - ## Assumes list of ggplots - p <- list(...) - } else if (is.list(..1)) { - ## Assumes list with list of ggplots - p <- ..1 - } else { - cli::cli_abort("Can only align {.cls ggplot} objects or a list of them") - } - - yr <- clean_common_axis(p, "y") - - xr <- clean_common_axis(p, "x") - - suppressWarnings({ - p_out <- purrr::map(p, \(.x) { - out <- .x - if (isTRUE(x.axis)) { - out <- out + ggplot2::xlim(xr) - } - if (isTRUE(y.axis)) { - out <- out + ggplot2::ylim(yr) - } - out - }) - }) - - if (isTRUE(percentage)) { - lapply(p_out, \(.x) { - .x + - ggplot2::scale_y_continuous(labels = scales::percent) - }) - } else { - p_out - } -} - -#' Extract and clean axis ranges -#' -#' @param p plot -#' @param axis axis. x or y. -#' -#' @returns vector -#' @export -#' -clean_common_axis <- function(p, axis) { - purrr::map(p, ~ ggplot2::layer_scales(.x)[[axis]]$get_limits()) |> - unlist() |> - (\(.x) { - if (is.numeric(.x)) { - range(.x) - } else { - as.character(.x) - } - })() |> - unique() -} - - ######## #### Current file: /Users/au301842/FreesearchR/R//redcap_read_shiny_module.R ######## @@ -12469,7 +12003,7 @@ ui_elements <- function(selection) { "Read more on how ", tags$a( "data types", - href = "https://freesearchr.github.io/FreesearchR-knowledge/app/data_types.html", + href = "https://agdamsbo.github.io/FreesearchR/articles/data-types.html", target = "_blank", rel = "noopener noreferrer" ), @@ -12916,7 +12450,7 @@ ui_elements <- function(selection) { "docs" = bslib::nav_item( # shiny::img(shiny::icon("book")), shiny::tags$a( - href = "https://freesearchr.github.io/FreesearchR-knowledge/", + href = "https://agdamsbo.github.io/FreesearchR/", "Docs", shiny::icon("arrow-up-right-from-square"), target = "_blank", diff --git a/app_docker/translations/translation_da.csv b/app_docker/translations/translation_da.csv index 517df60d..927131ba 100644 --- a/app_docker/translations/translation_da.csv +++ b/app_docker/translations/translation_da.csv @@ -89,6 +89,7 @@ "and","og" "from each pair","fra hvert par" "Plot","Tegn" +"Adjust settings, then press ""Plot"".","Juster indstillingerne og tryk så ""Tegn""." "Plot height (mm)","Højde af grafik (mm)" "Plot width (mm)","Bredde af grafik (mm)" "File format","File format" @@ -96,7 +97,12 @@ "Select variable","Vælg variabel" "Response variable","Svarvariable" "Plot type","Type af grafik" +"Please select","Vælg" +"Additional variables","Yderligere variabler" +"Secondary variable","Sekundær variabel" "No variable","Ingen variabel" +"Grouping variable","Variabel til gruppering" +"No stratification","Ingen stratificering" "Drawing the plot. Hold tight for a moment..","Tegner grafikken. Spænd selen.." "#Plotting\n","#Tegner\n" "Stacked horizontal bars","Stablede horisontale søjler" @@ -304,6 +310,7 @@ "Sample data","Sample data" "Settings","Settings" "Create new factor","Create new factor" +"Choose color palette","Choose color palette" "Optional filter logic (e.g., ⁠[gender] = 'female')","Optional filter logic (e.g., ⁠[gender] = 'female')" "Drop empty","Drop empty" "Choose variable:","Choose variable:" @@ -313,14 +320,3 @@ "Modify factor","Modify factor" "Create factor/categorical variable from other variables.","Create factor/categorical variable from other variables." "The data set has %s obs. in %s variables.","The data set has %s obs. in %s variables." -"Adjust plot input and settings below, then press ""Plot"".","Adjust plot input and settings below, then press ""Plot""." -"Define plot","Define plot" -"Choose color palette","Choose color palette" -"Additional variable","Additional variable" -"Grouping variable","Grouping variable" -"Secondary variable","Secondary variable" -"Reverse colors","Reverse colors" -"Plot survey results","Plot survey results" -"Additional variables","Additional variables" -"Other variables","Other variables" -"Select variables and plot type,\nthen click 'Plot' to generate visualization","Select variables and plot type,\nthen click 'Plot' to generate visualization" diff --git a/app_docker/translations/translation_sw.csv b/app_docker/translations/translation_sw.csv index c56e9549..134ec155 100644 --- a/app_docker/translations/translation_sw.csv +++ b/app_docker/translations/translation_sw.csv @@ -89,6 +89,7 @@ "and","na" "from each pair","kutoka kwa kila jozi" "Plot","Kipande cha habari" +"Adjust settings, then press ""Plot"".","Rekebisha mipangilio, kisha bonyeza ""Plot""." "Plot height (mm)","Urefu wa kiwanja (mm)" "Plot width (mm)","Upana wa kiwanja (mm)" "File format","Umbizo la faili" @@ -96,7 +97,12 @@ "Select variable","Chagua kigezo" "Response variable","Kigezo cha majibu" "Plot type","Aina ya kiwanja" +"Please select","Tafadhali chagua" +"Additional variables","Vigezo vya ziada" +"Secondary variable","Kigezo cha pili" "No variable","Hakuna kigezo" +"Grouping variable","Kigezo cha kuweka katika makundi" +"No stratification","Hakuna matabaka" "Drawing the plot. Hold tight for a moment..","Kuchora njama. Shikilia kwa muda.." "#Plotting\n","#Upangaji\n" "Stacked horizontal bars","Pau za mlalo zilizopangwa kwa mpangilio" @@ -304,6 +310,7 @@ "Sample data","Sample data" "Settings","Settings" "Create new factor","Create new factor" +"Choose color palette","Choose color palette" "Optional filter logic (e.g., ⁠[gender] = 'female')","Optional filter logic (e.g., ⁠[gender] = 'female')" "Drop empty","Drop empty" "Choose variable:","Choose variable:" @@ -313,14 +320,3 @@ "Modify factor","Modify factor" "Create factor/categorical variable from other variables.","Create factor/categorical variable from other variables." "The data set has %s obs. in %s variables.","The data set has %s obs. in %s variables." -"Adjust plot input and settings below, then press ""Plot"".","Adjust plot input and settings below, then press ""Plot""." -"Define plot","Define plot" -"Choose color palette","Choose color palette" -"Additional variable","Additional variable" -"Grouping variable","Grouping variable" -"Secondary variable","Secondary variable" -"Reverse colors","Reverse colors" -"Plot survey results","Plot survey results" -"Additional variables","Additional variables" -"Other variables","Other variables" -"Select variables and plot type,\nthen click 'Plot' to generate visualization","Select variables and plot type,\nthen click 'Plot' to generate visualization" diff --git a/examples/visuals_module_demo.R b/examples/visuals_module_demo.R index e4883d6c..00a8c020 100644 --- a/examples/visuals_module_demo.R +++ b/examples/visuals_module_demo.R @@ -22,7 +22,7 @@ visuals_demo_app <- function() { ) ) server <- function(input, output, session) { - pl <- data_visuals_server("visuals", data = shiny::reactive(default_parsing(mtcars)),palettes = color_choices()) + pl <- data_visuals_server("visuals", data = shiny::reactive(default_parsing(mtcars))) } shiny::shinyApp(ui, server) } diff --git a/inst/apps/FreesearchR/app.R b/inst/apps/FreesearchR/app.R index 7baeb26b..fbadebb2 100644 --- a/inst/apps/FreesearchR/app.R +++ b/inst/apps/FreesearchR/app.R @@ -1,7 +1,7 @@ ######## -#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//RtmpAe8F1F/file150d9fbea069.R +#### Current file: /var/folders/9l/xbc19wxx0g79jdd2sf_0v291mhwh7f/T//RtmprUCGcI/file47614d090a4c.R ######## i18n_path <- system.file("translations", package = "FreesearchR") @@ -64,7 +64,7 @@ i18n$set_translation_language("en") #### Current file: /Users/au301842/FreesearchR/R//app_version.R ######## -app_version <- function()'26.6.1' +app_version <- function()'26.4.2' ######## @@ -2151,23 +2151,12 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { list( bslib::layout_sidebar( sidebar = bslib::sidebar( - shiny::actionButton( - inputId = ns("act_plot"), - label = i18n$t("Plot"), - width = "100%", - icon = phosphoricons::ph("paint-brush", weight = "bold"), - # icon = shiny::icon("palette"), - disabled = FALSE - ), - shiny::helpText( - i18n$t('Adjust plot input and settings below, then press "Plot".') - ), bslib::accordion( id = "acc_plot", multiple = FALSE, bslib::accordion_panel( value = "acc_pan_plot", - title = i18n$t("Define plot"), + title = "Create plot", icon = phosphoricons::ph("chart-line"), # icon = bsicons::bs_icon("graph-up"), shiny::uiOutput(outputId = ns("primary")), @@ -2178,16 +2167,19 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { ), shiny::tags$br(), shiny::uiOutput(outputId = ns("type")), - shiny::h5(i18n$t("Other variables")), shiny::uiOutput(outputId = ns("secondary")), - shiny::uiOutput(outputId = ns("tertiary")) - ), - bslib::accordion_panel( - value = "acc_pan_params", - title = i18n$t("Settings"), - icon = phosphoricons::ph("gear"), + shiny::uiOutput(outputId = ns("tertiary")), shiny::uiOutput(outputId = ns("color_palette")), - shiny::uiOutput(outputId = ns("basic_parameters")), + shiny::br(), + shiny::actionButton( + inputId = ns("act_plot"), + label = i18n$t("Plot"), + width = "100%", + icon = phosphoricons::ph("paint-brush",weight = "bold"), + # icon = shiny::icon("palette"), + disabled = FALSE + ), + shiny::helpText(i18n$t('Adjust settings, then press "Plot".')) ), bslib::accordion_panel( value = "acc_pan_download", @@ -2240,14 +2232,14 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { shiny::p( "We have collected a few notes on visualising data and details on the options included in FreesearchR:", shiny::tags$a( - href = "https://freesearchr.github.io/FreesearchR-knowledge/app/visuals.html", + href = "https://agdamsbo.github.io/FreesearchR/articles/visuals.html", "View notes in new tab", target = "_blank", rel = "noopener noreferrer" ) ) ), - shiny::plotOutput(ns("plot"), height = "65vh"), + shiny::plotOutput(ns("plot"), height = "70vh"), shiny::tags$br(), shiny::tags$br(), shiny::htmlOutput(outputId = ns("code_plot")) @@ -2264,7 +2256,10 @@ data_visuals_ui <- function(id, tab_title = "Plots", ...) { #' @name data-plots #' @returns shiny server module #' @export -data_visuals_server <- function(id, data, palettes = color_choices(), ...) { +data_visuals_server <- function(id, + data, + palettes, + ...) { shiny::moduleServer( id = id, module = function(input, output, session) { @@ -2316,99 +2311,69 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { plot_data <- data()[input$primary] } - plots <- possible_plots(data = plot_data, source_list = available_plots()) + plots <- possible_plots(data = plot_data) - plots_named <- get_input_params(plots) |> + plots_named <- get_plot_options(plots) |> lapply(\(.x) { stats::setNames(.x$descr, .x$note) }) - # plots_named <- get_plot_options(plots) |> - # lapply(\(.x) { - # stats::setNames(.x$descr, .x$note) - # }) - vectorSelectInput( inputId = ns("type"), selected = NULL, - label = shiny::h5(i18n$t("Plot type")), + label = shiny::h4(i18n$t("Plot type")), choices = Reduce(c, plots_named), multiple = FALSE ) }) rv$plot.params <- shiny::reactive({ - get_input_params(input$type) |> purrr::pluck(1) - # get_plot_options(input$type) |> purrr::pluck(1) + get_plot_options(input$type) |> purrr::pluck(1) }) - - ### Include two additional variable inputs output$secondary <- shiny::renderUI({ shiny::req(input$type) - # Get the plot function name - base_params <- rv$plot.params()[["base"]] + cols <- c(rv$plot.params()[["secondary.extra"]], all_but(colnames( + subset_types(data(), rv$plot.params()[["secondary.type"]]) + ), input$primary)) - filtered_params <- base_params[sapply(base_params, function(params) { - params$id %in% "secondary" - })][[1]] - - filtered_params$exclude <- input$primary - - create_input_element( - input_id = "secondary", - ns = ns, - params = append_list(data(), filtered_params, "data") + columnSelectInput( + inputId = ns("secondary"), + data = data, + selected = cols[1], + placeholder = i18n$t("Please select"), + label = if (isTRUE(rv$plot.params()[["secondary.multi"]])) + i18n$t("Additional variables") + else + i18n$t("Secondary variable"), + multiple = rv$plot.params()[["secondary.multi"]], + maxItems = rv$plot.params()[["secondary.max"]], + col_subset = cols, + none_label = i18n$t("No variable") ) - }) output$tertiary <- shiny::renderUI({ shiny::req(input$type) - # Get the plot function name - base_params <- rv$plot.params()[["base"]] - - filtered_params <- base_params[sapply(base_params, function(params) { - params$id %in% "tertiary" - })][[1]] - - filtered_params$exclude <- c(input$primary, input$secondary) - - create_input_element( - input_id = "tertiary", - ns = ns, - params = append_list(data(), filtered_params, "data") + columnSelectInput( + inputId = ns("tertiary"), + data = data, + placeholder = i18n$t("Please select"), + label = i18n$t("Grouping variable"), + multiple = FALSE, + col_subset = c( + "none", + all_but( + colnames(subset_types(data(), rv$plot.params()[["tertiary.type"]])), + input$primary, + input$secondary + ) + ), + none_label = i18n$t("No stratification") ) }) - - ### Generating additional parameter inputs if any specified - output$basic_parameters <- renderUI({ - req(input$type, rv$plot.params) - - # Get the plot function name - base_params <- rv$plot.params()[["base"]] - - filtered_params <- base_params[sapply(base_params, function(params) { - !params$id %in% c("secondary", "tertiary") - })] - - - # Create UI elements for base parameters - base_inputs <- lapply(filtered_params, function(params) { - input_id <- paste0("base_", params$id) - params$id <- NULL - if (params$type %in% "select_variables") { - params$data <- data() - } - - create_input_element(params, ns, input_id) - }) - tagList(base_inputs) - - }) - ### Color option output$color_palette <- shiny::renderUI({ # shiny::req(input$type) @@ -2422,49 +2387,19 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { shiny::observeEvent(input$act_plot, { if (NROW(data()) > 0) { - tryCatch({ - # Get all input values with prefixes - base_inputs <- reactiveValuesToList(input)[grep("^base_", names(reactiveValuesToList(input)))] - # advanced_inputs <- reactiveValuesToList(input)[grep("^advanced_", names(reactiveValuesToList(input)))] - - # Remove the prefix from names - names(base_inputs) <- gsub("^base_", "", names(base_inputs)) - # names(advanced_inputs) <- gsub("^advanced_", "", names(advanced_inputs)) - - base_inputs <- c(base_inputs, - list(color.palette = input$color_palette)) - - # If any of the specified parameters are NULL/missing, the settings - # accordion/panel was never opened, and they can be ignored, as - # default settings will the be used. - if (any(sapply(base_inputs, is.null))) { - dynamic_params <- list() - } else { - dynamic_params <- base_inputs - } - - # Build parameters for plotting function + tryCatch({ parameters <- list( type = rv$plot.params()[["fun"]], pri = input$primary, sec = input$secondary, - ter = input$tertiary + ter = input$tertiary, + color.palette = input$color_palette ) - parameters <- modifyList(parameters, dynamic_params) - ## If the dictionary holds additional arguments to pass to the ## plotting function, these are included if (!is.null(rv$plot.params()[["fun.args"]])) { - default_params <- rv$plot.params()[["fun.args"]] - - ## Ensure not to overwrite user defined parameters are overwritten - ## This allows to define default parameters. - ## - ## This will create a strange edge case, where the plot looks in - ## one way, when plotted initially, but may change, when the settings - ## accordion is opened. Problem for future me. Really mostly an edge case. - parameters <- modifyList(parameters, default_params[!names(default_params) %in% names(parameters)]) + parameters <- modifyList(parameters, rv$plot.params()[["fun.args"]]) } shiny::withProgress(message = i18n$t("Drawing the plot. Hold tight for a moment.."), @@ -2500,25 +2435,7 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { if (!is.null(rv$plot)) { rv$plot } else { - # Create a placeholder plot with instructions using ggplot2 - ggplot2::ggplot() + - ggplot2::annotate( - "text", - x = 0.5, - y = 0.5, - label = i18n$t("Select variables and plot type,\nthen click 'Plot' to generate visualization"), - size = 5, - color = "gray50", - lineheight = 0.8 - ) + - ggplot2::xlim(0, 1) + - ggplot2::ylim(0, 1) + - ggplot2::theme_void() + - ggplot2::theme( - panel.background = ggplot2::element_rect(fill = "white"), - plot.background = ggplot2::element_rect(fill = "white") - ) - # return(NULL) + return(NULL) } }) @@ -2563,6 +2480,506 @@ data_visuals_server <- function(id, data, palettes = color_choices(), ...) { ) } +#' Select all from vector but +#' +#' @param data vector +#' @param ... exclude +#' +#' @returns vector +#' @export +#' +#' @examples +#' all_but(1:10, c(2, 3), 11, 5) +all_but <- function(data, ...) { + data[!data %in% c(...)] +} + +#' Easily subset by data type function +#' +#' @param data data +#' @param types desired types +#' @param type.fun function to get type. Default is outcome_type +#' +#' @returns vector +#' @export +#' +#' @examples +#' default_parsing(mtcars) |> subset_types("ordinal") +#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical")) +#' #' default_parsing(mtcars) |> subset_types("factor",class) +subset_types <- function(data, types, type.fun = data_type) { + data[sapply(data, type.fun) %in% types] +} + + +#' Implemented functions +#' +#' @description +#' Library of supported functions. The list name and "descr" element should be +#' unique for each element on list. +#' +#' - descr: Plot description +#' +#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal) +#' +#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal) +#' +#' - secondary.extra: "none" or NULL to have option to choose none. +#' +#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal) +#' +#' +#' @returns list +#' @export +#' +#' @examples +#' supported_plots() |> str() +supported_plots <- function() { + list( + plot_bar_rel = list( + fun = "plot_bar", + fun.args = list(style = "fill"), + descr = i18n$t("Stacked relative barplot"), + note = i18n$t( + "Create relative stacked barplots to show the distribution of categorical levels" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ), + plot_bar_abs = list( + fun = "plot_bar", + fun.args = list(style = "dodge"), + descr = i18n$t("Side-by-side barplot"), + note = i18n$t( + "Create side-by-side barplot to show the distribution of categorical levels" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_hbars = list( + fun = "plot_hbars", + descr = i18n$t("Stacked horizontal bars"), + note = i18n$t( + "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_violin = list( + fun = "plot_violin", + descr = i18n$t("Violin plot"), + note = i18n$t( + "A modern alternative to the classic boxplot to visualise data distribution" + ), + primary.type = c("datatime", "continuous"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + secondary.extra = "none", + tertiary.type = c("dichotomous", "categorical") + ), + # plot_ridge = list( + # descr = "Ridge plot", + # note = "An alternative option to visualise data distribution", + # primary.type = "continuous", + # secondary.type = c("dichotomous" ,"categorical"), + # tertiary.type = c("dichotomous" ,"categorical"), + # secondary.extra = NULL + # ), + plot_sankey = list( + fun = "plot_sankey", + descr = i18n$t("Sankey plot"), + note = i18n$t("A way of visualising change between groups"), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + secondary.extra = NULL, + tertiary.type = c("dichotomous", "categorical") + ), + plot_scatter = list( + fun = "plot_scatter", + descr = i18n$t("Scatter plot"), + note = i18n$t("A classic way of showing the association between to variables"), + primary.type = c("datatime", "continuous"), + secondary.type = c("datatime", "continuous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ), + plot_box = list( + fun = "plot_box", + descr = i18n$t("Box plot"), + note = i18n$t("A classic way to plot data distribution by groups"), + primary.type = c("datatime", "continuous"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = FALSE, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = "none" + ), + plot_euler = list( + fun = "plot_euler", + descr = i18n$t("Euler diagram"), + note = i18n$t( + "Generate area-proportional Euler diagrams to display set relationships" + ), + primary.type = c("dichotomous"), + secondary.type = c("dichotomous"), + secondary.multi = TRUE, + secondary.max = 4, + tertiary.type = c("dichotomous"), + secondary.extra = NULL + ), + plot_euler = list( + fun = "plot_likert", + descr = i18n$t("Likert diagram"), + note = i18n$t( + "Plot survey results" + ), + primary.type = c("dichotomous", "categorical"), + secondary.type = c("dichotomous", "categorical"), + secondary.multi = TRUE, + secondary.extra = NULL, + tertiary.type = c("dichotomous", "categorical"), + secondary.extra = NULL + ) + ) +} + +#' Get possible regression models +#' +#' @param data data +#' +#' @returns character vector +#' @export +#' +#' @examples +#' mtcars |> +#' default_parsing() |> +#' dplyr::pull("cyl") |> +#' possible_plots() +#' +#' mtcars |> +#' default_parsing() |> +#' dplyr::select("mpg") |> +#' possible_plots() +possible_plots <- function(data) { + # browser() + # data <- if (is.reactive(data)) data() else data + if (is.data.frame(data)) { + data <- data[[1]] + } + + type <- data_type(data) + + if (type == "unknown") { + out <- type + } else { + out <- supported_plots() |> + lapply(\(.x) { + if (type %in% .x$primary.type) { + .x$descr + } + }) |> + unlist() + } + unname(out) +} + +#' Get the function options based on the selected function description +#' +#' @param data vector +#' +#' @returns list +#' @export +#' +#' @examples +#' ls <- mtcars |> +#' default_parsing() |> +#' dplyr::pull(mpg) |> +#' possible_plots() |> +#' (\(.x){ +#' .x[[1]] +#' })() |> +#' get_plot_options() +get_plot_options <- function(data) { + descrs <- supported_plots() |> + lapply(\(.x) { + .x$descr + }) |> + unlist() + supported_plots() |> + (\(.x) { + .x[match(data, descrs)] + })() +} + + + +#' Wrapper to create plot based on provided type +#' +#' @param data data.frame +#' @param pri primary variable +#' @param sec secondary variable +#' @param ter tertiary variable +#' @param type plot type (derived from possible_plots() and matches custom function) +#' @param color.palette choose color palette. See \code{\link{plot_colors}} for support. +#' @param ... ignored for now +#' +#' @name data-plots +#' +#' @returns ggplot2 object +#' @export +#' +#' @examples +#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes() +create_plot <- function(data, + type, + pri, + sec, + ter = NULL, + color.palette = "viridis", + ...) { + if (!is.null(sec)) { + if (!any(sec %in% names(data))) { + sec <- NULL + } + } + + if (!is.null(ter)) { + if (!ter %in% names(data)) { + ter <- NULL + } + } + + parameters <- list( + pri = pri, + sec = sec, + ter = ter, + color.palette = color.palette, + ... + ) + + out <- do.call(type, modifyList(parameters, list(data = data))) + + code <- rlang::call2(type, !!!parameters, .ns = "FreesearchR") + + attr(out, "code") <- code + out +} + +#' Print label, and if missing print variable name for plots +#' +#' @param data vector or data frame +#' @param var variable name. Optional. +#' +#' @returns character string +#' @export +#' +#' @examples +#' mtcars |> get_label(var = "mpg") +#' mtcars |> get_label() +#' mtcars$mpg |> get_label() +#' gtsummary::trial |> get_label(var = "trt") +#' gtsummary::trial$trt |> get_label() +#' 1:10 |> get_label() +get_label <- function(data, var = NULL) { + # data <- if (is.reactive(data)) data() else data + if (!is.null(var) & is.data.frame(data)) { + data <- data[[var]] + } + out <- REDCapCAST::get_attr(data = data, attr = "label") + if (is.na(out)) { + if (is.null(var)) { + out <- deparse(substitute(data)) + } else { + if (is.symbol(var)) { + out <- gsub('\"', "", deparse(substitute(var))) + } else { + out <- var + } + } + } + out +} + + +#' Line breaking at given number of characters for nicely plotting labels +#' +#' @param data string +#' @param lineLength maximum line length +#' @param fixed flag to force split at exactly the value given in lineLength. +#' Default is FALSE, only splitting at spaces. +#' +#' @returns character string +#' @export +#' +#' @examples +#' "Lorem ipsum... you know the routine" |> line_break() +#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE) +line_break <- function(data, + lineLength = 20, + force = FALSE) { + if (isTRUE(force)) { + ## This eats some letters when splitting a sentence... ?? + gsub(paste0("(.{1,", lineLength, "})(\\s|[[:alnum:]])"), + "\\1\n", + data) + } else { + paste(strwrap(data, lineLength), collapse = "\n") + } + ## https://stackoverflow.com/a/29847221 +} + + +#' Wrapping +#' +#' @param data list of ggplot2 objects +#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL +#' @param title panel title +#' @param guides passed to patchwork::wrap_plots() +#' @param axes passed to patchwork::wrap_plots() +#' @param axis_titles passed to patchwork::wrap_plots() +#' @param ... passed to patchwork::wrap_plots() +#' +#' @returns list of ggplot2 objects +#' @export +#' +wrap_plot_list <- function(data, + tag_levels = NULL, + title = NULL, + axis.font.family = NULL, + guides = "collect", + axes = "collect", + axis_titles = "collect", + y.axis.percentage = FALSE, + ...) { + if (ggplot2::is_ggplot(data[[1]])) { + if (length(data) > 1) { + out <- data |> + (\(.x) { + if (rlang::is_named(.x)) { + purrr::imap(.x, \(.y, .i) { + .y + ggplot2::ggtitle(.i) + }) + } else { + .x + } + })() |> + align_axes(percentage=y.axis.percentage) |> + patchwork::wrap_plots(guides = guides, + axes = axes, + axis_titles = axis_titles, + ...) + if (!is.null(tag_levels)) { + out <- out + patchwork::plot_annotation(tag_levels = tag_levels) + } + if (!is.null(title)) { + out <- out + + patchwork::plot_annotation( + title = title, + theme = ggplot2::theme(plot.title = ggplot2::element_text(size = 25)) + ) + } + } else { + out <- data[[1]] + } + } else { + cli::cli_abort("Can only wrap lists of {.cls ggplot} objects") + } + + if (!is.null(axis.font.family)) { + if (inherits(x = out, what = "patchwork")) { + out <- out & + ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) + } else { + out <- out + + ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) + } + } + + out +} + + +#' Aligns axes between plots +#' +#' @param ... ggplot2 objects or list of ggplot2 objects +#' +#' @returns list of ggplot2 objects +#' @export +#' +align_axes <- function(..., + x.axis = TRUE, + y.axis = TRUE, + percentage = FALSE) { + # https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object + # https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150 + if (ggplot2::is_ggplot(..1)) { + ## Assumes list of ggplots + p <- list(...) + } else if (is.list(..1)) { + ## Assumes list with list of ggplots + p <- ..1 + } else { + cli::cli_abort("Can only align {.cls ggplot} objects or a list of them") + } + + yr <- clean_common_axis(p, "y") + + xr <- clean_common_axis(p, "x") + + suppressWarnings({ + p_out <- purrr::map(p, \(.x) { + out <- .x + if (isTRUE(x.axis)) { + out <- out + ggplot2::xlim(xr) + } + if (isTRUE(y.axis)) { + out <- out + ggplot2::ylim(yr) + } + out + }) + }) + + if(isTRUE(percentage)){ + lapply(p_out,\(.x){ + .x+ + ggplot2::scale_y_continuous(labels = scales::percent) + }) + } else { + p_out + } +} + +#' Extract and clean axis ranges +#' +#' @param p plot +#' @param axis axis. x or y. +#' +#' @returns vector +#' @export +#' +clean_common_axis <- function(p, axis) { + purrr::map(p, ~ ggplot2::layer_scales(.x)[[axis]]$get_limits()) |> + unlist() |> + (\(.x) { + if (is.numeric(.x)) { + range(.x) + } else { + as.character(.x) + } + })() |> + unique() +} + ######## #### Current file: /Users/au301842/FreesearchR/R//data-summary.R @@ -3428,25 +3845,38 @@ footer_ui <- function(i18n) { #' #' @export generate_colors <- function(n, palette = "viridis", ...) { - - # --- Input validation ------------------------------------------------------- - if (!is.numeric(n) || length(n) != 1 || n < 1 || n %% 1 != 0) { + if (!is.numeric(n) || + length(n) != 1 || n < 1 || n != as.integer(n)) { stop("`n` must be a single positive integer.") } - if (!is.function(palette) && (!is.character(palette) || length(palette) != 1)) { - stop("`palette` must be a single character string or a function.") - } - # --- Function passthrough --------------------------------------------------- + # Function passthrough — call directly with n and ... if (is.function(palette)) { return(palette(n, ...)) } - # --- Named palette dispatch ------------------------------------------------- + if (!is.character(palette) || length(palette) != 1) { + stop("`palette` must be a single character string or a function.") + } + + if (!is.numeric(n) || + length(n) != 1 || n < 1 || n != as.integer(n)) { + stop("`n` must be a single positive integer.") + } + if (!is.character(palette) || length(palette) != 1) { + stop("`palette` must be a single character string.") + } + palette_lower <- tolower(palette) - viridis_palettes <- c("viridis", "magma", "plasma", "inferno", - "cividis", "mako", "rocket", "turbo") + viridis_palettes <- c("viridis", + "magma", + "plasma", + "inferno", + "cividis", + "mako", + "rocket", + "turbo") if (palette_lower %in% viridis_palettes) { viridisLite::viridis(n = n, option = palette_lower, ...) @@ -3466,42 +3896,35 @@ generate_colors <- function(n, palette = "viridis", ...) { } else if (palette_lower == "topo") { grDevices::topo.colors(n = n, ...) + } else if (palette %in% rownames(RColorBrewer::brewer.pal.info)) { + max_n <- RColorBrewer::brewer.pal.info[palette, "maxcolors"] + fetch_n <- max(min(n, max_n), 3L) # clamp to [3, max_n] for brewer.pal() + base_colors <- RColorBrewer::brewer.pal(n = fetch_n, name = palette) + grDevices::colorRampPalette(base_colors)(n) + + } else if (palette %in% grDevices::palette.pals()) { + grDevices::colorRampPalette(palette.colors(palette = palette))(n) + + } else if (palette %in% grDevices::hcl.pals()) { + grDevices::hcl.colors(n = n, palette = palette, ...) + } else { - # Case-insensitive RColorBrewer lookup - brewer_names <- rownames(RColorBrewer::brewer.pal.info) - brewer_match <- brewer_names[match(palette_lower, tolower(brewer_names))] - - if (!is.na(brewer_match)) { - max_n <- RColorBrewer::brewer.pal.info[brewer_match, "maxcolors"] - fetch_n <- max(min(n, max_n), 3L) - base_colors <- RColorBrewer::brewer.pal(n = fetch_n, name = brewer_match) - grDevices::colorRampPalette(base_colors)(n) - - } else { - # Case-insensitive grDevices palette.pals() lookup - pal_names <- grDevices::palette.pals() - pal_match <- pal_names[match(palette_lower, tolower(pal_names))] - - if (!is.na(pal_match)) { - grDevices::colorRampPalette(grDevices::palette.colors(palette = pal_match))(n) - - } else if (palette %in% grDevices::hcl.pals()) { - # Named HCL palettes (e.g. "Rocket", "Plasma") — distinct from viridisLite - grDevices::hcl.colors(n = n, palette = palette, ...) - - } else { - warning( - "Unknown palette: '", palette, "'. Falling back to viridis.\n", - "Available options:\n", - " viridisLite : viridis, magma, plasma, inferno, cividis, mako, rocket, turbo\n", - " grDevices : hcl, rainbow, heat, terrain, topo\n", - " grDevices HCL: use grDevices::hcl.pals() to see all options\n", - " grDevices : use grDevices::palette.pals() to see all options\n", - " RColorBrewer : use RColorBrewer::brewer.pal.info to see all options" - ) - viridisLite::viridis(n = n, option = "viridis") - } - } + message( + paste0( + "Unknown palette: '", + palette, + "'. ", + "Falling back to default R colors.\n", + "Available options:\n", + " viridisLite : viridis, magma, plasma, inferno, cividis, mako, rocket, turbo\n", + " grDevices : hcl, rainbow, heat, terrain, topo\n", + " grDevices HCL: use grDevices::hcl.pals() to see all options\n", + " grDevices : use grDevices::palette.pals() to see all options\n", + " RColorBrewer : use RColorBrewer::brewer.pal.info to see all options" + ) + ) + viridisLite::viridis(n = n, option = "viridis") + # grDevices::hcl.colors(n = n) } } @@ -4534,7 +4957,7 @@ apply_idea_filter <- function(filtered_reactive, df_target, env = parent.frame() #### Current file: /Users/au301842/FreesearchR/R//hosted_version.R ######## -hosted_version <- function()'v26.6.1' +hosted_version <- function()'v26.4.2-260410' ######## @@ -6548,14 +6971,14 @@ plot_bar <- function(data, sec = sec, style = style, max_level = max_level, - color.palette = color.palette, - ... + color.palette = color.palette ) }) wrap_plot_list(out, title = glue::glue(i18n$t("Grouped by {get_label(data,ter)}")), - y.axis.percentage = TRUE) + y.axis.percentage = TRUE, + ...) } @@ -6676,11 +7099,11 @@ plot_box <- function(data, pri, sec, ter = NULL,color.palette="viridis",...) { data = .ds, pri = pri, sec = sec, - color.palette=color.palette, ... + color.palette=color.palette ) }) - wrap_plot_list(out,title=glue::glue(i18n$t("Grouped by {get_label(data,ter)}"))) + wrap_plot_list(out,title=glue::glue(i18n$t("Grouped by {get_label(data,ter)}")),...) } @@ -6874,7 +7297,7 @@ plot_euler <- function(data, pri, sec, ter = NULL, seed = 2103,color.palette="vi #' D = sample(c(TRUE, FALSE, FALSE, FALSE), 50, TRUE) #' ) |> plot_euler_single() #' mtcars[c("vs", "am")] |> plot_euler_single("magma") -plot_euler_single <- function(data,color.palette="viridis", ...) { +plot_euler_single <- function(data,color.palette="viridis") { data |> ggeulerr(shape = "circle") + @@ -6917,15 +7340,13 @@ plot_hbars <- function(data, pri, sec, ter = NULL, - color.palette = "viridis", - ...) { + color.palette = "viridis") { vertical_stacked_bars( data = data, score = pri, group = sec, strata = ter, - color.palette = color.palette, - ... + color.palette = color.palette ) } @@ -6978,7 +7399,7 @@ vertical_stacked_bars <- function(data, colors <- generate_colors(n = nrow(df.table), palette = color.palette) ## Colors are reversed by default as that usually gives the best result - if (isTRUE(reverse) | reverse=="TRUE") { + if (isTRUE(reverse)) { colors <- rev(colors) } @@ -7035,8 +7456,7 @@ plot_likert <- function(data, pri, sec = NULL, ter = NULL, - color.palette = "viridis", - ...) { + color.palette = "viridis") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -7213,8 +7633,7 @@ plot_sankey <- function(data, default.color = "#2986cc", box.color = "#1E4B66", na.color = "grey80", - missing.level = "Missing", - ...) { + missing.level = "Missing") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -7448,7 +7867,7 @@ color_levels_gen <- function(data,na.color="grey80",palette="viridis"){ #' @examples #' mtcars |> plot_scatter(pri = "mpg", sec = "wt") #' mtcars |> plot_scatter(pri = "mpg", sec = "wt",ter="carb") -plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis", ...) { +plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis") { if (is.null(ter)) { rempsyc::nice_scatter( data = data, @@ -7485,7 +7904,7 @@ plot_scatter <- function(data, pri, sec, ter = NULL, color.palette="viridis", .. #' @examples #' mtcars |> plot_violin(pri = "mpg", sec = "cyl") #' mtcars |> plot_violin(pri = "mpg", sec = "cyl", ter = "gear", color.palette="Blues") -plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis", ...) { +plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis") { if (!is.null(ter)) { ds <- split(data, data[ter]) } else { @@ -7500,8 +7919,7 @@ plot_violin <- function(data, pri, sec, ter = NULL, color.palette="viridis", ... group = sec, response = pri, xtitle = get_label(data, var = sec), - ytitle = get_label(data, var = pri), - ... + ytitle = get_label(data, var = pri) )+ scale_fill_generate(palette=color.palette) }) @@ -7647,890 +8065,6 @@ plot_download_demo_app <- function() { # plot_download_demo_app() -######## -#### Current file: /Users/au301842/FreesearchR/R//plot-helpers.R -######## - -#' Implemented functions -#' -#' @description -#' Library of supported functions. The list name and "descr" element should be -#' unique for each element on list. -#' -#' - fun: the plotting function -#' -#' - fun.args: default parameters for the plotting function -#' -#' - descr: Plot description -#' -#' - note: Short note/description of the function for displaying in ui and docs -#' -#' - primary.type: Primary variable data type (see [data_type]) -#' -#' - base: holds a list of parameters for plot input fields generation -#' Secondary and tertiary variable input fields are mandatory. -#' -#' -#' @returns list -#' @export -#' -#' @examples -#' available_plots() |> str() -available_plots <- function() { - list( - plot_bar_rel = list( - fun = "plot_bar", - fun.args = list(style = "fill"), - descr = i18n$t("Stacked relative barplot"), - note = i18n$t( - "Create relative stacked barplots to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Additional variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_bar_abs = list( - fun = "plot_bar", - fun.args = list(style = "dodge"), - descr = i18n$t("Side-by-side barplot"), - note = i18n$t( - "Create side-by-side barplot to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_hbars = list( - fun = "plot_hbars", - descr = i18n$t("Stacked horizontal bars"), - note = i18n$t( - "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" - ), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ), - list( - id = "reverse", - type = "select_input", - label = i18n$t("Reverse colors"), - choices = c(yes = TRUE, no = FALSE) - ) - ), - advanced = list() - ######### - ), - plot_violin = list( - fun = "plot_violin", - descr = i18n$t("Violin plot"), - note = i18n$t( - "A modern alternative to the classic boxplot to visualise data distribution" - ), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_sankey = list( - fun = "plot_sankey", - descr = i18n$t("Sankey plot"), - note = i18n$t("A way of visualising change between groups"), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_scatter = list( - fun = "plot_scatter", - descr = i18n$t("Scatter plot"), - note = i18n$t("A classic way of showing the association between to variables"), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("datatime", "continuous", "categorical"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_box = list( - fun = "plot_box", - descr = i18n$t("Box plot"), - note = i18n$t("A classic way to plot data distribution by groups"), - primary.type = c("datatime", "continuous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = FALSE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_euler = list( - fun = "plot_euler", - descr = i18n$t("Euler diagram"), - note = i18n$t( - "Generate area-proportional Euler diagrams to display set relationships" - ), - primary.type = c("dichotomous"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous"), - allow_none = FALSE, - # inputId = "sec", - label = i18n$t("Secondary variable"), - multiple = TRUE, - maxItems = 4 - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ), - plot_likert = list( - fun = "plot_likert", - descr = i18n$t("Likert diagram"), - note = i18n$t("Plot survey results"), - primary.type = c("dichotomous", "categorical"), - ### Input definitions ### - base = list( - list( - id = "secondary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - allow_none = TRUE, - # inputId = "sec", - label = i18n$t("Additional variables"), - multiple = TRUE - ), - list( - id = "tertiary", - type = "select_variables", - var_types = c("dichotomous", "categorical"), - # inputId = "sec", - label = i18n$t("Grouping variable"), - multiple = FALSE - ) - ), - advanced = list() - ######### - ) - ) -} - -# Helper function to create input elements dynamically -create_input_element <- function(params, ns, input_id) { - # Add the namespaced inputId to the arguments - params$inputId <- ns(input_id) - - # Map input types to Shiny functions - input_function <- switch( - params$type, - "numeric_input" = shiny::numericInput, - "select_input" = shiny::selectInput, - "checkbox_input" = shiny::checkboxInput, - "slider_input" = shiny::sliderInput, - "text_input" = shiny::textInput, - "select_variables" = selectPlotVariables - ) - - params$type <- NULL - params$id <- NULL - - - # Call the function with all arguments - do.call(input_function, params) -} - -#' Wrapper for columnSelectInput -#' -selectPlotVariables <- function(data, - exclude = NULL, - allow_none = TRUE, - var_types, - ...) { - datar <- if (is.reactive(data)) { - data - } else { - reactive(data) - } - - cols <- all_but(colnames(subset_types(datar(), var_types)), exclude) - - if (isTRUE(allow_none)) { - cols <- c("none", cols) - } - - params <- list(...) - - params$none_label <- i18n$t("No variable") - params$col_subset <- cols - - rlang::exec(columnSelectInput, !!!append_list(datar(), params, "data")) -} - - - -#' Select all from vector but -#' -#' @param data vector -#' @param ... exclude -#' -#' @returns vector -#' @export -#' -#' @examples -#' all_but(1:10, c(2, 3), 11, 5) -all_but <- function(data, ...) { - data[!data %in% c(...)] -} - -#' Easily subset by data type function -#' -#' @param data data -#' @param types desired types -#' @param type.fun function to get type. Default is outcome_type -#' -#' @returns vector -#' @export -#' -#' @examples -#' default_parsing(mtcars) |> subset_types("ordinal") -#' default_parsing(mtcars) |> subset_types(c("dichotomous", "categorical")) -#' #' default_parsing(mtcars) |> subset_types("factor",class) -subset_types <- function(data, types, type.fun = data_type) { - data[sapply(data, type.fun) %in% types] -} - - -#' Implemented functions -#' -#' @description -#' Library of supported functions. The list name and "descr" element should be -#' unique for each element on list. -#' -#' - descr: Plot description -#' -#' - primary.type: Primary variable data type (continuous, dichotomous or ordinal) -#' -#' - secondary.type: Secondary variable data type (continuous, dichotomous or ordinal) -#' -#' - secondary.extra: "none" or NULL to have option to choose none. -#' -#' - tertiary.type: Tertiary variable data type (continuous, dichotomous or ordinal) -#' -#' -#' @returns list -#' @export -#' -#' @examples -#' supported_plots() |> str() -supported_plots <- function() { - list( - plot_bar_rel = list( - fun = "plot_bar", - fun.args = list(style = "fill"), - descr = i18n$t("Stacked relative barplot"), - note = i18n$t( - "Create relative stacked barplots to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ), - plot_bar_abs = list( - fun = "plot_bar", - fun.args = list(style = "dodge"), - descr = i18n$t("Side-by-side barplot"), - note = i18n$t( - "Create side-by-side barplot to show the distribution of categorical levels" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_hbars = list( - fun = "plot_hbars", - descr = i18n$t("Stacked horizontal bars"), - note = i18n$t( - "A classical way of visualising the distribution of an ordinal scale like the modified Ranking Scale and known as Grotta bars" - ), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_violin = list( - fun = "plot_violin", - descr = i18n$t("Violin plot"), - note = i18n$t( - "A modern alternative to the classic boxplot to visualise data distribution" - ), - primary.type = c("datatime", "continuous"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - secondary.extra = "none", - tertiary.type = c("dichotomous", "categorical") - ), - # plot_ridge = list( - # descr = "Ridge plot", - # note = "An alternative option to visualise data distribution", - # primary.type = "continuous", - # secondary.type = c("dichotomous" ,"categorical"), - # tertiary.type = c("dichotomous" ,"categorical"), - # secondary.extra = NULL - # ), - plot_sankey = list( - fun = "plot_sankey", - descr = i18n$t("Sankey plot"), - note = i18n$t("A way of visualising change between groups"), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - secondary.extra = NULL, - tertiary.type = c("dichotomous", "categorical") - ), - plot_scatter = list( - fun = "plot_scatter", - descr = i18n$t("Scatter plot"), - note = i18n$t("A classic way of showing the association between to variables"), - primary.type = c("datatime", "continuous"), - secondary.type = c("datatime", "continuous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ), - plot_box = list( - fun = "plot_box", - descr = i18n$t("Box plot"), - note = i18n$t("A classic way to plot data distribution by groups"), - primary.type = c("datatime", "continuous"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = FALSE, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = "none" - ), - plot_euler = list( - fun = "plot_euler", - descr = i18n$t("Euler diagram"), - note = i18n$t( - "Generate area-proportional Euler diagrams to display set relationships" - ), - primary.type = c("dichotomous"), - secondary.type = c("dichotomous"), - secondary.multi = TRUE, - secondary.max = 4, - tertiary.type = c("dichotomous"), - secondary.extra = NULL - ), - plot_likert = list( - fun = "plot_likert", - descr = i18n$t("Likert diagram"), - note = i18n$t("Plot survey results"), - primary.type = c("dichotomous", "categorical"), - secondary.type = c("dichotomous", "categorical"), - secondary.multi = TRUE, - secondary.extra = NULL, - tertiary.type = c("dichotomous", "categorical"), - secondary.extra = NULL - ) - ) -} - -#' Get possible regression models -#' -#' @param data data -#' -#' @returns character vector -#' @export -#' -#' @examples -#' mtcars |> -#' default_parsing() |> -#' dplyr::pull("cyl") |> -#' possible_plots() -#' -#' mtcars |> -#' default_parsing() |> -#' dplyr::select("mpg") |> -#' possible_plots() -possible_plots <- function(data, source_list = supported_plots()) { - # browser() - # data <- if (is.reactive(data)) data() else data - if (is.data.frame(data)) { - data <- data[[1]] - } - - type <- data_type(data) - - if (type == "unknown") { - out <- type - } else { - out <- source_list |> - lapply(\(.x) { - if (type %in% .x$primary.type) { - .x$descr - } - }) |> - unlist() - } - unname(out) -} - -#' Get the function options based on the selected function description -#' -#' @param data vector -#' -#' @returns list -#' @export -#' -#' @examples -#' ls <- mtcars |> -#' default_parsing() |> -#' dplyr::pull(mpg) |> -#' possible_plots() |> -#' (\(.x){ -#' .x[[1]] -#' })() |> -#' get_plot_options() -get_plot_options <- function(data) { - descrs <- supported_plots() |> - lapply(\(.x) { - .x$descr - }) |> - unlist() - supported_plots() |> - (\(.x) { - .x[match(data, descrs)] - })() -} - -#' Get the function parameters based on the selected function description -#' -#' @param data vector -#' -#' @returns list -#' @export -#' -#' @examples -#' ls <- mtcars |> -#' default_parsing() |> -#' dplyr::pull(mpg) |> -#' possible_plots() |> -#' (\(.x){ -#' .x[[1]] -#' })() |> -#' get_input_params() -get_input_params <- function(data) { - descr <- available_plots() |> - lapply(\(.x) { - .x$descr - }) |> - unlist() - available_plots() |> - (\(.x) { - .x[match(data, descr)] - })() -} - - -#' Wrapper to create plot based on provided type -#' -#' @param data data.frame -#' @param pri primary variable -#' @param sec secondary variable -#' @param ter tertiary variable -#' @param type plot type (derived from possible_plots() and matches custom function) -#' @param color.palette choose color palette. See \code{\link{plot_colors}} for support. -#' @param ... ignored for now -#' -#' @name data-plots -#' -#' @returns ggplot2 object -#' @export -#' -#' @examples -#' create_plot(mtcars, "plot_violin", "mpg", "cyl") |> attributes() -create_plot <- function(data, - type, - pri, - sec, - ter = NULL, - color.palette = "viridis", - ...) { - if (!is.null(sec)) { - if (!any(sec %in% names(data))) { - sec <- NULL - } - } - - if (!is.null(ter)) { - if (!ter %in% names(data)) { - ter <- NULL - } - } - - parameters <- list( - pri = pri, - sec = sec, - ter = ter, - color.palette = color.palette, - ... - ) - - out <- do.call(type, modifyList(parameters, list(data = data))) - - code <- rlang::call2(type, !!!parameters, .ns = "FreesearchR") - - attr(out, "code") <- code - out -} - -#' Print label, and if missing print variable name for plots -#' -#' @param data vector or data frame -#' @param var variable name. Optional. -#' -#' @returns character string -#' @export -#' -#' @examples -#' mtcars |> get_label(var = "mpg") -#' mtcars |> get_label() -#' mtcars$mpg |> get_label() -#' gtsummary::trial |> get_label(var = "trt") -#' gtsummary::trial$trt |> get_label() -#' 1:10 |> get_label() -get_label <- function(data, var = NULL) { - # data <- if (is.reactive(data)) data() else data - if (!is.null(var) & is.data.frame(data)) { - data <- data[[var]] - } - out <- REDCapCAST::get_attr(data = data, attr = "label") - if (is.na(out)) { - if (is.null(var)) { - out <- deparse(substitute(data)) - } else { - if (is.symbol(var)) { - out <- gsub('\"', "", deparse(substitute(var))) - } else { - out <- var - } - } - } - out -} - - -#' Line breaking at given number of characters for nicely plotting labels -#' -#' @param data string -#' @param lineLength maximum line length -#' @param fixed flag to force split at exactly the value given in lineLength. -#' Default is FALSE, only splitting at spaces. -#' -#' @returns character string -#' @export -#' -#' @examples -#' "Lorem ipsum... you know the routine" |> line_break() -#' paste(sample(letters[1:10], 100, TRUE), collapse = "") |> line_break(force = TRUE) -line_break <- function(data, - lineLength = 20, - force = FALSE) { - if (isTRUE(force)) { - ## This eats some letters when splitting a sentence... ?? - gsub(paste0("(.{1,", lineLength, "})(\\s|[[:alnum:]])"), - "\\1\n", - data) - } else { - paste(strwrap(data, lineLength), collapse = "\n") - } - ## https://stackoverflow.com/a/29847221 -} - - -#' Wrapping -#' -#' @param data list of ggplot2 objects -#' @param tag_levels passed to patchwork::plot_annotation if given. Default is NULL -#' @param title panel title -#' @param guides passed to patchwork::wrap_plots() -#' @param axes passed to patchwork::wrap_plots() -#' @param axis_titles passed to patchwork::wrap_plots() -#' @param ... passed to patchwork::wrap_plots() -#' -#' @returns list of ggplot2 objects -#' @export -#' -wrap_plot_list <- function(data, - tag_levels = NULL, - title = NULL, - axis.font.family = NULL, - guides = "collect", - axes = "collect", - axis_titles = "collect", - y.axis.percentage = FALSE, - ...) { - if (ggplot2::is_ggplot(data[[1]])) { - if (length(data) > 1) { - out <- data |> - (\(.x) { - if (rlang::is_named(.x)) { - purrr::imap(.x, \(.y, .i) { - .y + ggplot2::ggtitle(.i) - }) - } else { - .x - } - })() |> - align_axes(percentage = y.axis.percentage) |> - patchwork::wrap_plots(guides = guides, - axes = axes, - axis_titles = axis_titles, - ...) - if (!is.null(tag_levels)) { - out <- out + patchwork::plot_annotation(tag_levels = tag_levels) - } - if (!is.null(title)) { - out <- out + - patchwork::plot_annotation( - title = title, - theme = ggplot2::theme(plot.title = ggplot2::element_text(size = 25)) - ) - } - } else { - out <- data[[1]] - } - } else { - cli::cli_abort("Can only wrap lists of {.cls ggplot} objects") - } - - if (!is.null(axis.font.family)) { - if (inherits(x = out, what = "patchwork")) { - out <- out & - ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) - } else { - out <- out + - ggplot2::theme(axis.text = ggplot2::element_text(family = axis.font.family)) - } - } - - out -} - - -#' Aligns axes between plots -#' -#' @param ... ggplot2 objects or list of ggplot2 objects -#' -#' @returns list of ggplot2 objects -#' @export -#' -align_axes <- function(..., - x.axis = TRUE, - y.axis = TRUE, - percentage = FALSE) { - # https://stackoverflow.com/questions/62818776/get-axis-limits-from-ggplot-object - # https://github.com/thomasp85/patchwork/blob/main/R/plot_multipage.R#L150 - if (ggplot2::is_ggplot(..1)) { - ## Assumes list of ggplots - p <- list(...) - } else if (is.list(..1)) { - ## Assumes list with list of ggplots - p <- ..1 - } else { - cli::cli_abort("Can only align {.cls ggplot} objects or a list of them") - } - - yr <- clean_common_axis(p, "y") - - xr <- clean_common_axis(p, "x") - - suppressWarnings({ - p_out <- purrr::map(p, \(.x) { - out <- .x - if (isTRUE(x.axis)) { - out <- out + ggplot2::xlim(xr) - } - if (isTRUE(y.axis)) { - out <- out + ggplot2::ylim(yr) - } - out - }) - }) - - if (isTRUE(percentage)) { - lapply(p_out, \(.x) { - .x + - ggplot2::scale_y_continuous(labels = scales::percent) - }) - } else { - p_out - } -} - -#' Extract and clean axis ranges -#' -#' @param p plot -#' @param axis axis. x or y. -#' -#' @returns vector -#' @export -#' -clean_common_axis <- function(p, axis) { - purrr::map(p, ~ ggplot2::layer_scales(.x)[[axis]]$get_limits()) |> - unlist() |> - (\(.x) { - if (is.numeric(.x)) { - range(.x) - } else { - as.character(.x) - } - })() |> - unique() -} - - ######## #### Current file: /Users/au301842/FreesearchR/R//redcap_read_shiny_module.R ######## @@ -12469,7 +12003,7 @@ ui_elements <- function(selection) { "Read more on how ", tags$a( "data types", - href = "https://freesearchr.github.io/FreesearchR-knowledge/app/data_types.html", + href = "https://agdamsbo.github.io/FreesearchR/articles/data-types.html", target = "_blank", rel = "noopener noreferrer" ), @@ -12916,7 +12450,7 @@ ui_elements <- function(selection) { "docs" = bslib::nav_item( # shiny::img(shiny::icon("book")), shiny::tags$a( - href = "https://freesearchr.github.io/FreesearchR-knowledge/", + href = "https://agdamsbo.github.io/FreesearchR/", "Docs", shiny::icon("arrow-up-right-from-square"), target = "_blank", diff --git a/inst/translations/translation_da.csv b/inst/translations/translation_da.csv index 517df60d..927131ba 100644 --- a/inst/translations/translation_da.csv +++ b/inst/translations/translation_da.csv @@ -89,6 +89,7 @@ "and","og" "from each pair","fra hvert par" "Plot","Tegn" +"Adjust settings, then press ""Plot"".","Juster indstillingerne og tryk så ""Tegn""." "Plot height (mm)","Højde af grafik (mm)" "Plot width (mm)","Bredde af grafik (mm)" "File format","File format" @@ -96,7 +97,12 @@ "Select variable","Vælg variabel" "Response variable","Svarvariable" "Plot type","Type af grafik" +"Please select","Vælg" +"Additional variables","Yderligere variabler" +"Secondary variable","Sekundær variabel" "No variable","Ingen variabel" +"Grouping variable","Variabel til gruppering" +"No stratification","Ingen stratificering" "Drawing the plot. Hold tight for a moment..","Tegner grafikken. Spænd selen.." "#Plotting\n","#Tegner\n" "Stacked horizontal bars","Stablede horisontale søjler" @@ -304,6 +310,7 @@ "Sample data","Sample data" "Settings","Settings" "Create new factor","Create new factor" +"Choose color palette","Choose color palette" "Optional filter logic (e.g., ⁠[gender] = 'female')","Optional filter logic (e.g., ⁠[gender] = 'female')" "Drop empty","Drop empty" "Choose variable:","Choose variable:" @@ -313,14 +320,3 @@ "Modify factor","Modify factor" "Create factor/categorical variable from other variables.","Create factor/categorical variable from other variables." "The data set has %s obs. in %s variables.","The data set has %s obs. in %s variables." -"Adjust plot input and settings below, then press ""Plot"".","Adjust plot input and settings below, then press ""Plot""." -"Define plot","Define plot" -"Choose color palette","Choose color palette" -"Additional variable","Additional variable" -"Grouping variable","Grouping variable" -"Secondary variable","Secondary variable" -"Reverse colors","Reverse colors" -"Plot survey results","Plot survey results" -"Additional variables","Additional variables" -"Other variables","Other variables" -"Select variables and plot type,\nthen click 'Plot' to generate visualization","Select variables and plot type,\nthen click 'Plot' to generate visualization" diff --git a/inst/translations/translation_sw.csv b/inst/translations/translation_sw.csv index c56e9549..134ec155 100644 --- a/inst/translations/translation_sw.csv +++ b/inst/translations/translation_sw.csv @@ -89,6 +89,7 @@ "and","na" "from each pair","kutoka kwa kila jozi" "Plot","Kipande cha habari" +"Adjust settings, then press ""Plot"".","Rekebisha mipangilio, kisha bonyeza ""Plot""." "Plot height (mm)","Urefu wa kiwanja (mm)" "Plot width (mm)","Upana wa kiwanja (mm)" "File format","Umbizo la faili" @@ -96,7 +97,12 @@ "Select variable","Chagua kigezo" "Response variable","Kigezo cha majibu" "Plot type","Aina ya kiwanja" +"Please select","Tafadhali chagua" +"Additional variables","Vigezo vya ziada" +"Secondary variable","Kigezo cha pili" "No variable","Hakuna kigezo" +"Grouping variable","Kigezo cha kuweka katika makundi" +"No stratification","Hakuna matabaka" "Drawing the plot. Hold tight for a moment..","Kuchora njama. Shikilia kwa muda.." "#Plotting\n","#Upangaji\n" "Stacked horizontal bars","Pau za mlalo zilizopangwa kwa mpangilio" @@ -304,6 +310,7 @@ "Sample data","Sample data" "Settings","Settings" "Create new factor","Create new factor" +"Choose color palette","Choose color palette" "Optional filter logic (e.g., ⁠[gender] = 'female')","Optional filter logic (e.g., ⁠[gender] = 'female')" "Drop empty","Drop empty" "Choose variable:","Choose variable:" @@ -313,14 +320,3 @@ "Modify factor","Modify factor" "Create factor/categorical variable from other variables.","Create factor/categorical variable from other variables." "The data set has %s obs. in %s variables.","The data set has %s obs. in %s variables." -"Adjust plot input and settings below, then press ""Plot"".","Adjust plot input and settings below, then press ""Plot""." -"Define plot","Define plot" -"Choose color palette","Choose color palette" -"Additional variable","Additional variable" -"Grouping variable","Grouping variable" -"Secondary variable","Secondary variable" -"Reverse colors","Reverse colors" -"Plot survey results","Plot survey results" -"Additional variables","Additional variables" -"Other variables","Other variables" -"Select variables and plot type,\nthen click 'Plot' to generate visualization","Select variables and plot type,\nthen click 'Plot' to generate visualization" diff --git a/man/align_axes.Rd b/man/align_axes.Rd index f403e1a7..6d3e79e2 100644 --- a/man/align_axes.Rd +++ b/man/align_axes.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{align_axes} \alias{align_axes} \title{Aligns axes between plots} diff --git a/man/all_but.Rd b/man/all_but.Rd index 8dc3f46e..e2453d15 100644 --- a/man/all_but.Rd +++ b/man/all_but.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{all_but} \alias{all_but} \title{Select all from vector but} diff --git a/man/available_plots.Rd b/man/available_plots.Rd deleted file mode 100644 index 0ee1d5ac..00000000 --- a/man/available_plots.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R -\name{available_plots} -\alias{available_plots} -\title{Implemented functions} -\usage{ -available_plots() -} -\value{ -list -} -\description{ -Library of supported functions. The list name and "descr" element should be -unique for each element on list. -\itemize{ -\item fun: the plotting function -\item fun.args: default parameters for the plotting function -\item descr: Plot description -\item note: Short note/description of the function for displaying in ui and docs -\item primary.type: Primary variable data type (see \link{data_type}) -\item base: holds a list of parameters for plot input fields generation -Secondary and tertiary variable input fields are mandatory. -} -} -\examples{ -available_plots() |> str() -} diff --git a/man/clean_common_axis.Rd b/man/clean_common_axis.Rd index 67197d46..175053c9 100644 --- a/man/clean_common_axis.Rd +++ b/man/clean_common_axis.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{clean_common_axis} \alias{clean_common_axis} \title{Extract and clean axis ranges} diff --git a/man/data-plots.Rd b/man/data-plots.Rd index e6d84e08..4222466f 100644 --- a/man/data-plots.Rd +++ b/man/data-plots.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data_plots.R, R/plot-helpers.R, R/plot_bar.R, -% R/plot_box.R, R/plot_hbar.R, R/plot_likert.R, R/plot_ridge.R, -% R/plot_sankey.R, R/plot_scatter.R, R/plot_violin.R +% Please edit documentation in R/data_plots.R, R/plot_bar.R, R/plot_box.R, +% R/plot_hbar.R, R/plot_likert.R, R/plot_ridge.R, R/plot_sankey.R, +% R/plot_scatter.R, R/plot_violin.R \name{data-plots} \alias{data-plots} \alias{data_visuals_ui} @@ -22,7 +22,7 @@ \usage{ data_visuals_ui(id, tab_title = "Plots", ...) -data_visuals_server(id, data, palettes = color_choices(), ...) +data_visuals_server(id, data, palettes, ...) create_plot(data, type, pri, sec, ter = NULL, color.palette = "viridis", ...) @@ -50,9 +50,9 @@ plot_box(data, pri, sec, ter = NULL, color.palette = "viridis", ...) plot_box_single(data, pri, sec = NULL, seed = 2103, color.palette = "viridis") -plot_hbars(data, pri, sec, ter = NULL, color.palette = "viridis", ...) +plot_hbars(data, pri, sec, ter = NULL, color.palette = "viridis") -plot_likert(data, pri, sec = NULL, ter = NULL, color.palette = "viridis", ...) +plot_likert(data, pri, sec = NULL, ter = NULL, color.palette = "viridis") plot_ridge(data, x, y, z = NULL, color.palette = "viridis", ...) @@ -69,13 +69,12 @@ plot_sankey( default.color = "#2986cc", box.color = "#1E4B66", na.color = "grey80", - missing.level = "Missing", - ... + missing.level = "Missing" ) -plot_scatter(data, pri, sec, ter = NULL, color.palette = "viridis", ...) +plot_scatter(data, pri, sec, ter = NULL, color.palette = "viridis") -plot_violin(data, pri, sec, ter = NULL, color.palette = "viridis", ...) +plot_violin(data, pri, sec, ter = NULL, color.palette = "viridis") } \arguments{ \item{id}{Module id. (Use 'ns("id")')} diff --git a/man/get_input_params.Rd b/man/get_input_params.Rd deleted file mode 100644 index 6766d73e..00000000 --- a/man/get_input_params.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R -\name{get_input_params} -\alias{get_input_params} -\title{Get the function parameters based on the selected function description} -\usage{ -get_input_params(data) -} -\arguments{ -\item{data}{vector} -} -\value{ -list -} -\description{ -Get the function parameters based on the selected function description -} -\examples{ -ls <- mtcars |> - default_parsing() |> - dplyr::pull(mpg) |> - possible_plots() |> - (\(.x){ - .x[[1]] - })() |> - get_input_params() -} diff --git a/man/get_label.Rd b/man/get_label.Rd index c808209e..108fd372 100644 --- a/man/get_label.Rd +++ b/man/get_label.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{get_label} \alias{get_label} \title{Print label, and if missing print variable name for plots} diff --git a/man/get_plot_options.Rd b/man/get_plot_options.Rd index 83001d38..08c04496 100644 --- a/man/get_plot_options.Rd +++ b/man/get_plot_options.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{get_plot_options} \alias{get_plot_options} \title{Get the function options based on the selected function description} diff --git a/man/line_break.Rd b/man/line_break.Rd index d926556e..65c987c7 100644 --- a/man/line_break.Rd +++ b/man/line_break.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{line_break} \alias{line_break} \title{Line breaking at given number of characters for nicely plotting labels} diff --git a/man/plot_euler_single.Rd b/man/plot_euler_single.Rd index 22d425c2..f481d5af 100644 --- a/man/plot_euler_single.Rd +++ b/man/plot_euler_single.Rd @@ -4,7 +4,7 @@ \alias{plot_euler_single} \title{Easily plot single euler diagrams} \usage{ -plot_euler_single(data, color.palette = "viridis", ...) +plot_euler_single(data, color.palette = "viridis") } \value{ ggplot2 object diff --git a/man/possible_plots.Rd b/man/possible_plots.Rd index d1519e38..28c0b623 100644 --- a/man/possible_plots.Rd +++ b/man/possible_plots.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{possible_plots} \alias{possible_plots} \title{Get possible regression models} \usage{ -possible_plots(data, source_list = supported_plots()) +possible_plots(data) } \arguments{ \item{data}{data} diff --git a/man/selectPlotVariables.Rd b/man/selectPlotVariables.Rd deleted file mode 100644 index f9e63e5d..00000000 --- a/man/selectPlotVariables.Rd +++ /dev/null @@ -1,11 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R -\name{selectPlotVariables} -\alias{selectPlotVariables} -\title{Wrapper for columnSelectInput} -\usage{ -selectPlotVariables(data, exclude = NULL, allow_none = TRUE, var_types, ...) -} -\description{ -Wrapper for columnSelectInput -} diff --git a/man/subset_types.Rd b/man/subset_types.Rd index a33e1561..61fced5e 100644 --- a/man/subset_types.Rd +++ b/man/subset_types.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{subset_types} \alias{subset_types} \title{Easily subset by data type function} diff --git a/man/supported_plots.Rd b/man/supported_plots.Rd index caa250e3..c91ad753 100644 --- a/man/supported_plots.Rd +++ b/man/supported_plots.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{supported_plots} \alias{supported_plots} \title{Implemented functions} diff --git a/man/wrap_plot_list.Rd b/man/wrap_plot_list.Rd index dcf1ae64..40cf0ba1 100644 --- a/man/wrap_plot_list.Rd +++ b/man/wrap_plot_list.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-helpers.R +% Please edit documentation in R/data_plots.R \name{wrap_plot_list} \alias{wrap_plot_list} \title{Wrapping}