FreesearchR/R/data_plots.R

408 lines
13 KiB
R
Raw Normal View History

# source(here::here("functions.R"))
#' Data correlations evaluation module
#'
#' @param id Module id. (Use 'ns("id")')
#'
#' @name data-plots
#' @returns Shiny ui module
#' @export
#'
2025-03-05 21:13:06 +01:00
data_visuals_ui <- function(id, tab_title = "Plots", ...) {
ns <- shiny::NS(id)
list(
2025-07-03 16:19:51 +02:00
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".')
),
2025-07-03 16:19:51 +02:00
bslib::accordion(
2025-09-23 12:24:43 +02:00
id = "acc_plot",
2025-07-03 16:19:51 +02:00
multiple = FALSE,
bslib::accordion_panel(
2025-09-23 12:24:43 +02:00
value = "acc_pan_plot",
title = i18n$t("Define plot"),
icon = phosphoricons::ph("chart-line"),
# icon = bsicons::bs_icon("graph-up"),
2025-07-03 16:19:51 +02:00
shiny::uiOutput(outputId = ns("primary")),
shiny::helpText(
i18n$t(
'Only non-text variables are available for plotting. Go the "Data" to reclass data to plot.'
)
),
2025-07-03 16:19:51 +02:00
shiny::tags$br(),
shiny::uiOutput(outputId = ns("type")),
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("color_palette")),
shiny::uiOutput(outputId = ns("basic_parameters")),
),
2025-07-03 16:19:51 +02:00
bslib::accordion_panel(
2025-09-23 12:24:43 +02:00
value = "acc_pan_download",
2025-07-03 16:19:51 +02:00
title = "Download",
icon = phosphoricons::ph("download-simple"),
# icon = bsicons::bs_icon("download"),
2025-07-03 16:19:51 +02:00
shinyWidgets::noUiSliderInput(
inputId = ns("height_slide"),
2025-09-11 15:21:04 +02:00
label = i18n$t("Plot height (mm)"),
2025-07-03 16:19:51 +02:00
min = 50,
max = 300,
value = 100,
step = 1,
format = shinyWidgets::wNumbFormat(decimals = 0),
color = datamods:::get_primary_color(),
inline = TRUE
),
# shiny::numericInput(
# inputId = ns("height_numeric"),
# label = "Plot height (mm)",
# min = 50,
# max = 300,
# value = 100
# ),
shinyWidgets::noUiSliderInput(
inputId = ns("width"),
2025-09-11 15:21:04 +02:00
label = i18n$t("Plot width (mm)"),
2025-07-03 16:19:51 +02:00
min = 50,
max = 300,
value = 100,
step = 1,
format = shinyWidgets::wNumbFormat(decimals = 0),
color = datamods:::get_primary_color()
),
shiny::selectInput(
inputId = ns("plot_type"),
2025-09-11 15:21:04 +02:00
label = i18n$t("File format"),
choices = list("png", "tiff", "eps", "pdf", "jpeg", "svg")
2025-07-03 16:19:51 +02:00
),
shiny::br(),
# Button
shiny::downloadButton(
outputId = ns("download_plot"),
2025-09-11 15:21:04 +02:00
label = i18n$t("Download plot"),
icon = phosphoricons::ph("arrow-fat-down")
# icon = shiny::icon("download")
)
)
2025-07-03 16:19:51 +02:00
),
shiny::p(
"We have collected a few notes on visualising data and details on the options included in FreesearchR:",
shiny::tags$a(
href = "https://agdamsbo.github.io/FreesearchR/articles/visuals.html",
"View notes in new tab",
target = "_blank",
rel = "noopener noreferrer"
)
)
2025-07-03 16:19:51 +02:00
),
shiny::plotOutput(ns("plot"), height = "70vh"),
2025-04-09 12:31:08 +02:00
shiny::tags$br(),
shiny::tags$br(),
shiny::htmlOutput(outputId = ns("code_plot"))
)
)
2025-07-03 16:19:51 +02:00
# )
}
#'
#' @param data data
#' @param ... ignored
#'
#' @name data-plots
#' @returns shiny server module
#' @export
data_visuals_server <- function(id, data, palettes = color_choices(), ...) {
shiny::moduleServer(
id = id,
module = function(input, output, session) {
ns <- session$ns
rv <- shiny::reactiveValues(plot.params = NULL,
plot = NULL,
code = NULL)
2025-09-23 12:24:43 +02:00
shiny::observe({
bslib::accordion_panel_update(
id = "acc_plot",
target = "acc_pan_plot",
title = i18n$t("Create plot")
)
bslib::accordion_panel_update(id = "acc_plot",
target = "acc_pan_download",
title = i18n$t("Download"))
2025-09-23 12:24:43 +02:00
})
output$primary <- shiny::renderUI({
shiny::req(data())
columnSelectInput(
inputId = ns("primary"),
col_subset = names(data())[sapply(data(), data_type) != "text"],
data = data,
2025-09-11 15:21:04 +02:00
placeholder = i18n$t("Select variable"),
label = i18n$t("Response variable"),
multiple = FALSE
)
})
# shiny::observeEvent(data, {
# if (is.null(data()) | NROW(data()) == 0) {
2026-03-31 20:42:22 +02:00
# shiny::updateActionButton(inputId = "act_plot", disabled = TRUE)
# } else {
2026-03-31 20:42:22 +02:00
# shiny::updateActionButton(inputId = "act_plot", disabled = FALSE)
# }
# })
output$type <- shiny::renderUI({
shiny::req(input$primary)
shiny::req(data())
# browser()
if (!input$primary %in% names(data())) {
plot_data <- data()[1]
} else {
plot_data <- data()[input$primary]
}
plots <- possible_plots(data = plot_data, source_list = available_plots())
2026-05-29 11:46:58 +02:00
plots_named <- get_input_params(plots) |>
lapply(\(.x) {
stats::setNames(.x$descr, .x$note)
2025-03-12 18:27:46 +01:00
})
2026-05-29 11:46:58 +02:00
# plots_named <- get_plot_options(plots) |>
# lapply(\(.x) {
# stats::setNames(.x$descr, .x$note)
# })
2025-03-12 18:27:46 +01:00
vectorSelectInput(
inputId = ns("type"),
selected = NULL,
2025-09-11 15:21:04 +02:00
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)
2026-05-29 11:46:58 +02:00
# get_plot_options(input$type) |> purrr::pluck(1)
})
### Include two additional variable inputs
output$secondary <- shiny::renderUI({
shiny::req(input$type)
2025-03-12 18:27:46 +01:00
2026-05-29 11:46:58 +02:00
# Get the plot function name
base_params <- rv$plot.params()[["base"]]
filtered_params <- base_params[sapply(base_params, function(params) {
params$id %in% "secondary"
})][[1]]
2026-05-29 11:46:58 +02:00
filtered_params$exclude <- input$primary
2026-05-29 11:46:58 +02:00
create_input_element(
input_id = "secondary",
ns = ns,
params = append_list(data(), filtered_params, "data")
)
2026-05-29 11:46:58 +02:00
})
2026-05-29 11:46:58 +02:00
output$tertiary <- shiny::renderUI({
shiny::req(input$type)
# Get the plot function name
base_params <- rv$plot.params()[["base"]]
2026-05-29 11:46:58 +02:00
filtered_params <- base_params[sapply(base_params, function(params) {
params$id %in% "tertiary"
})][[1]]
2026-05-29 11:46:58 +02:00
filtered_params$exclude <- c(input$primary, input$secondary)
2026-05-29 11:46:58 +02:00
create_input_element(
input_id = "tertiary",
ns = ns,
params = append_list(data(), filtered_params, "data")
)
})
### 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")
})]
2026-05-29 11:46:58 +02:00
# Create UI elements for base parameters
base_inputs <- lapply(filtered_params, function(params) {
2026-05-29 11:46:58 +02:00
input_id <- paste0("base_", params$id)
params$id <- NULL
if (params$type %in% "select_variables") {
params$data <- data()
}
2026-05-29 11:46:58 +02:00
create_input_element(params, ns, input_id)
})
tagList(base_inputs)
2026-05-29 11:46:58 +02:00
})
### Color option
output$color_palette <- shiny::renderUI({
# shiny::req(input$type)
colorSelectInput(
inputId = ns("color_palette"),
label = i18n$t("Choose color palette"),
2026-04-10 21:04:20 +02:00
choices = palettes,
previews = 5
)
})
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
}
2026-05-29 11:46:58 +02:00
# Build parameters for plotting function
parameters <- list(
type = rv$plot.params()[["fun"]],
pri = input$primary,
sec = input$secondary,
ter = input$tertiary
)
2026-05-29 11:46:58 +02:00
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)])
}
shiny::withProgress(message = i18n$t("Drawing the plot. Hold tight for a moment.."),
{
rv$plot <- rlang::exec(create_plot,
!!!append_list(data(), parameters, "data"))
})
rv$code <- glue::glue("FreesearchR::create_plot(df,{list2str(parameters)})")
}, # warning = function(warn) {
# showNotification(paste0(warn), type = "warning")
# },
error = function(err) {
2026-03-30 20:18:28 +02:00
showNotification(paste0(err), type = "error")
})
}
}, ignoreInit = TRUE)
output$code_plot <- shiny::renderUI({
shiny::req(rv$code)
2025-09-11 15:21:04 +02:00
prismCodeBlock(paste0(i18n$t("#Plotting\n"), rv$code))
2025-04-09 12:31:08 +02:00
})
shiny::observeEvent(list(data()), {
shiny::req(data())
2025-04-30 10:02:29 +02:00
rv$plot <- NULL
})
2025-04-30 10:02:29 +02:00
output$plot <- shiny::renderPlot({
2025-04-30 10:02:29 +02:00
# shiny::req(rv$plot)
# rv$plot
if (!is.null(rv$plot)) {
rv$plot
} else {
return(NULL)
}
})
2025-05-05 14:43:41 +02:00
# shiny::observeEvent(input$height_numeric, {
# shinyWidgets::updateNoUiSliderInput(session, ns("height_slide"), value = input$height_numeric)
# }, ignoreInit = TRUE)
# shiny::observeEvent(input$height_slide, {
# shiny::updateNumericInput(session, ns("height_numeric"), value = input$height_slide)
# }, ignoreInit = TRUE)
output$download_plot <- shiny::downloadHandler(
filename = shiny::reactive({
paste0("plot.", input$plot_type)
}),
content = function(file) {
2025-06-27 11:11:01 +02:00
if (inherits(rv$plot, "patchwork")) {
2025-05-05 14:43:41 +02:00
plot <- rv$plot
2025-06-27 11:11:01 +02:00
} else if (inherits(rv$plot, "ggplot")) {
2025-05-08 10:12:36 +02:00
plot <- rv$plot
2025-06-27 11:11:01 +02:00
} else {
2025-05-05 14:43:41 +02:00
plot <- rv$plot[[1]]
}
# browser()
2025-09-23 12:24:43 +02:00
shiny::withProgress(message = i18n$t("Drawing the plot. Hold tight for a moment.."), {
2025-03-05 21:13:06 +01:00
ggplot2::ggsave(
filename = file,
2025-05-05 14:43:41 +02:00
plot = plot,
2025-03-05 21:13:06 +01:00
width = input$width,
2025-05-05 14:43:41 +02:00
height = input$height_slide,
2025-03-05 21:13:06 +01:00
dpi = 300,
units = "mm",
scale = 2
2025-03-05 21:13:06 +01:00
)
})
}
)
shiny::observe(return(rv$plot))
}
)
}