|
| 1 | +#' Shiny module to render a gridify object with size controls |
| 2 | +#' |
| 3 | +#' @name gridify_with_settings |
| 4 | +#' @rdname gridify_with_settings |
| 5 | +#' |
| 6 | +#' @description |
| 7 | +#' A Shiny module that renders a [`gridifyClass-class`] object (from [gridify()]) |
| 8 | +#' with height and width sliders, plus PNG and PDF download buttons. |
| 9 | +#' |
| 10 | +#' @param id (`character(1)`) Shiny module id. |
| 11 | +#' |
| 12 | +#' @seealso [gridify()], [set_cell()], [gridifyLayout()] |
| 13 | +#' |
| 14 | +#' @examples |
| 15 | +#' if (interactive() && requireNamespace("shiny", quietly = TRUE) && |
| 16 | +#' requireNamespace("ggplot2", quietly = TRUE)) { |
| 17 | +#' library(shiny) |
| 18 | +#' library(ggplot2) |
| 19 | +#' library(gridify) |
| 20 | +#' |
| 21 | +#' ui <- fluidPage( |
| 22 | +#' gridify_with_settings_ui("demo") |
| 23 | +#' ) |
| 24 | +#' |
| 25 | +#' server <- function(input, output, session) { |
| 26 | +#' gridify_r <- reactive({ |
| 27 | +#' gridify( |
| 28 | +#' object = ggplot2::ggplot(mtcars, ggplot2::aes(mpg, wt)) + |
| 29 | +#' ggplot2::geom_point(), |
| 30 | +#' layout = simple_layout() |
| 31 | +#' ) |> |
| 32 | +#' set_cell("title", "My Plot") |
| 33 | +#' }) |
| 34 | +#' |
| 35 | +#' gridify_with_settings_srv("demo", gridify_r = gridify_r) |
| 36 | +#' } |
| 37 | +#' |
| 38 | +#' shinyApp(ui, server) |
| 39 | +#' } |
| 40 | +NULL |
| 41 | + |
| 42 | +#' @noRd |
| 43 | +gridify_require_shiny <- function() { |
| 44 | + if (!requireNamespace("shiny", quietly = TRUE)) { |
| 45 | + stop( |
| 46 | + "The 'shiny' package is required to use gridify_with_settings.\n", |
| 47 | + "Install it with: install.packages(\"shiny\")", |
| 48 | + call. = FALSE |
| 49 | + ) |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +#' @noRd |
| 54 | +assert_hwvec <- function(x, nm) { |
| 55 | + if (!is.numeric(x) || length(x) != 3L || any(!is.finite(x))) { |
| 56 | + stop( |
| 57 | + "'", nm, "' must be a numeric vector of length 3 (value, min, max).", |
| 58 | + call. = FALSE |
| 59 | + ) |
| 60 | + } |
| 61 | + if (x[1L] < x[2L] || x[1L] > x[3L]) { |
| 62 | + stop( |
| 63 | + "'", nm, "' value (", x[1L], ") must be between min (", x[2L], |
| 64 | + ") and max (", x[3L], ").", |
| 65 | + call. = FALSE |
| 66 | + ) |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +#' @rdname gridify_with_settings |
| 71 | +#' |
| 72 | +#' @return |
| 73 | +#' `gridify_with_settings_ui()` returns a `shiny::sidebarLayout`. |
| 74 | +#' |
| 75 | +#' @export |
| 76 | +gridify_with_settings_ui <- function(id) { |
| 77 | + gridify_require_shiny() |
| 78 | + if (!is.character(id) || length(id) != 1L) { |
| 79 | + stop("'id' must be a single character string.", call. = FALSE) |
| 80 | + } |
| 81 | + |
| 82 | + ns <- shiny::NS(id) |
| 83 | + |
| 84 | + shiny::sidebarLayout( |
| 85 | + sidebarPanel = shiny::sidebarPanel( |
| 86 | + width = 3, |
| 87 | + shiny::sliderInput( |
| 88 | + inputId = ns("height"), |
| 89 | + label = "Height (px)", |
| 90 | + min = 200L, |
| 91 | + max = 2000L, |
| 92 | + value = 600L, |
| 93 | + step = 10L, |
| 94 | + ticks = FALSE |
| 95 | + ), |
| 96 | + shiny::sliderInput( |
| 97 | + inputId = ns("width"), |
| 98 | + label = "Width (px)", |
| 99 | + min = 200L, |
| 100 | + max = 2000L, |
| 101 | + value = 800L, |
| 102 | + step = 10L, |
| 103 | + ticks = FALSE |
| 104 | + ), |
| 105 | + shiny::tags$hr(), |
| 106 | + shiny::downloadButton(ns("dl_png"), "PNG", class = "btn-sm"), |
| 107 | + shiny::tags$span(" "), |
| 108 | + shiny::downloadButton(ns("dl_pdf"), "PDF", class = "btn-sm") |
| 109 | + ), |
| 110 | + mainPanel = shiny::mainPanel( |
| 111 | + width = 9, |
| 112 | + shiny::uiOutput(ns("plot_ui")) |
| 113 | + ) |
| 114 | + ) |
| 115 | +} |
| 116 | + |
| 117 | +#' @rdname gridify_with_settings |
| 118 | +#' |
| 119 | +#' @param gridify_r (`reactive` or `function`) |
| 120 | +#' A `shiny::reactive()` (or plain function) returning a [`gridifyClass-class`] |
| 121 | +#' object produced by [gridify()]. |
| 122 | +#' |
| 123 | +#' @param height (`numeric(3)`) |
| 124 | +#' Height slider values `c(value, min, max)` in pixels. |
| 125 | +#' |
| 126 | +#' @param width (`numeric(3)`) |
| 127 | +#' Width slider values `c(value, min, max)` in pixels. |
| 128 | +#' |
| 129 | +#' @return |
| 130 | +#' `gridify_with_settings_srv()` invisibly returns `NULL`. |
| 131 | +#' |
| 132 | +#' @export |
| 133 | +gridify_with_settings_srv <- function( |
| 134 | + id, |
| 135 | + gridify_r, |
| 136 | + height = c(600L, 200L, 2000L), |
| 137 | + width = c(800L, 200L, 2000L)) { |
| 138 | + |
| 139 | + gridify_require_shiny() |
| 140 | + |
| 141 | + if (!is.character(id) || length(id) != 1L) { |
| 142 | + stop("'id' must be a single character string.", call. = FALSE) |
| 143 | + } |
| 144 | + if (!inherits(gridify_r, c("function", "reactive"))) { |
| 145 | + stop("'gridify_r' must be a shiny::reactive() or a plain function.", |
| 146 | + call. = FALSE) |
| 147 | + } |
| 148 | + |
| 149 | + assert_hwvec(height, "height") |
| 150 | + assert_hwvec(width, "width") |
| 151 | + |
| 152 | + shiny::moduleServer(id, function(input, output, session) { |
| 153 | + |
| 154 | + shiny::observe({ |
| 155 | + shiny::updateSliderInput( |
| 156 | + session, "height", |
| 157 | + min = height[2], max = height[3], value = height[1] |
| 158 | + ) |
| 159 | + shiny::updateSliderInput( |
| 160 | + session, "width", |
| 161 | + min = width[2], max = width[3], value = width[1] |
| 162 | + ) |
| 163 | + }) |> shiny::bindEvent(session$token, ignoreNULL = TRUE, once = TRUE) |
| 164 | + |
| 165 | + get_obj <- shiny::reactive({ |
| 166 | + obj <- if (inherits(gridify_r, "reactive")) gridify_r() else gridify_r() |
| 167 | + if (!methods::is(obj, "gridifyClass")) { |
| 168 | + stop( |
| 169 | + "gridify_with_settings_srv: 'gridify_r' must return a 'gridifyClass' object.\n", |
| 170 | + "Received class: ", paste(class(obj), collapse = ", "), |
| 171 | + call. = FALSE |
| 172 | + ) |
| 173 | + } |
| 174 | + obj |
| 175 | + }) |
| 176 | + |
| 177 | + p_height <- shiny::reactive(as.integer(input$height)) |
| 178 | + p_width <- shiny::reactive(as.integer(input$width)) |
| 179 | + |
| 180 | + output$plot_ui <- shiny::renderUI({ |
| 181 | + shiny::plotOutput( |
| 182 | + outputId = shiny::NS(id)("plot_out"), |
| 183 | + height = paste0(p_height(), "px"), |
| 184 | + width = paste0(p_width(), "px") |
| 185 | + ) |
| 186 | + }) |
| 187 | + |
| 188 | + output$plot_out <- shiny::renderPlot( |
| 189 | + expr = { |
| 190 | + methods::show(get_obj()) |
| 191 | + }, |
| 192 | + height = p_height, |
| 193 | + width = p_width |
| 194 | + ) |
| 195 | + |
| 196 | + output$dl_png <- shiny::downloadHandler( |
| 197 | + filename = function() { |
| 198 | + paste0("gridify_", format(Sys.time(), "%Y%m%d_%H%M%S"), ".png") |
| 199 | + }, |
| 200 | + content = function(file) { |
| 201 | + grDevices::png( |
| 202 | + filename = file, |
| 203 | + width = p_width(), |
| 204 | + height = p_height(), |
| 205 | + units = "px", |
| 206 | + res = 96L |
| 207 | + ) |
| 208 | + print(get_obj()) |
| 209 | + grDevices::dev.off() |
| 210 | + } |
| 211 | + ) |
| 212 | + |
| 213 | + output$dl_pdf <- shiny::downloadHandler( |
| 214 | + filename = function() { |
| 215 | + paste0("gridify_", format(Sys.time(), "%Y%m%d_%H%M%S"), ".pdf") |
| 216 | + }, |
| 217 | + content = function(file) { |
| 218 | + grDevices::pdf( |
| 219 | + file = file, |
| 220 | + width = p_width() / 96, |
| 221 | + height = p_height() / 96 |
| 222 | + ) |
| 223 | + print(get_obj()) |
| 224 | + grDevices::dev.off() |
| 225 | + } |
| 226 | + ) |
| 227 | + |
| 228 | + invisible(NULL) |
| 229 | + }) |
| 230 | +} |
0 commit comments