--- title: "Defaults and Hiding Controls" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Defaults and Hiding Controls} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` Two closely related arguments — `defaults` and `hide.inputs`/`hide.tabs` — let you control what users see and what the module starts with. This vignette covers both in depth. ## `defaults`: pre-filling inputs Pass a named list to the `defaults` argument of any `*InputsUI()` call. Each name is an input ID (matching the underlying plot function argument), and its value is what the control initialises to. Pass the same list to the module's `*Server()` too, so its **Reset** button returns the controls to these values rather than to the built-in ones. ```{r defaults-basic} library(VizModules) scatter_defaults <- list( x.by = "wt", y.by = "mpg", color.by = "cyl", size = 3, best.fit = TRUE ) ui <- fluidPage( sidebarLayout( sidebarPanel( dittoViz_scatterPlotInputsUI("p", mtcars, defaults = scatter_defaults) ), mainPanel(dittoViz_scatterPlotOutputUI("p")) ) ) server <- function(input, output, session) { dittoViz_scatterPlotServer("p", data = reactive(mtcars), defaults = scatter_defaults) } shinyApp(ui, server) ``` ### Finding valid keys Defaults keys map directly to the argument names of the underlying plot function. The quickest way to find them is the module's `*InputsUI()` help page, e.g. `?dittoViz_scatterPlotInputsUI`. The **Plot parameters and defaults** section lists every wired argument, its UI label, and its built-in default. For inputs that come from the uniform tab helpers (Axes, Legend, Lines, Plotly), see `?uniform_axes_inputs_ui`, `?uniform_legend_inputs_ui`, `?uniform_lines_inputs_ui`, and `?uniform_plotly_inputs_ui` respectively. ### Validation `get_default()` (used internally by every module) accepts an optional `validator` predicate. If the value you supply fails validation — e.g. passing a string where a logical is expected — the module silently falls back to its built-in default rather than erroring. This means a typo in a key is silent; double-check key names against the help page if a default appears to have no effect. ### Reactive defaults: driving inputs from app state Sometimes a parameter needs to *follow* your app rather than be fixed once. A common case is a colour mapping that should track a column the user has selected elsewhere in the app. For this, any individual entry in `defaults` may be a `reactive()` or a `reactiveVal()` instead of a plain value: ```{r defaults-reactive} library(VizModules) ui <- fluidPage( selectInput("colour_col", "Colour by", choices = c("cyl", "gear", "carb")), sidebarLayout( sidebarPanel(uiOutput("controls")), mainPanel(dittoViz_scatterPlotOutputUI("p")) ) ) server <- function(input, output, session) { plot_defaults <- list( x.by = "wt", y.by = "mpg", color.by = reactive(input$colour_col) ) output$controls <- renderUI({ dittoViz_scatterPlotInputsUI("p", mtcars, defaults = plot_defaults) }) dittoViz_scatterPlotServer("p", data = reactive(mtcars), defaults = plot_defaults) } shinyApp(ui, server) ``` Pass the same list to both `*InputsUI()` and `*Server()`, as you would for static defaults. Because the UI needs access to the reactive, build it inside `renderUI()` (the UI seed is taken with `isolate()`, so this does not cause the controls to re-render whenever the reactive changes). You get three guarantees: - **The parameter tracks the reactive.** Every change flows through to the plot. - **The control stays correct and editable.** The Color By selector shows the current column, and the user can still change it. - **The plot renders once per change.** The value is resolved server-side, in the same reactive flush as the data, so there is no flicker. Semantics worth knowing: - **An external change always wins.** If the user has picked a different colour column in the module and then changes the app-level selector, the selector's value replaces their choice. - **Reset restores the reactive's *current* value**, not the value it held at startup. - **Only `reactive()` and `reactiveVal()` are recognised.** A plain function is treated as a literal default value, not as something to call. - **Composite widgets sync cosmetically only.** The plot always uses the current value, but a compound control that does not accept a generic value message — `custom.models` in the scatter module is the one to watch — will not re-display it. Reactive defaults are not supported for that input. #### Why not `update*Input()` from the parent? The obvious alternative is an `observeEvent()` in the parent calling `updateTextInput(session, "p-main", ...)`. That works, but `update*Input()` is an asynchronous round-trip to the browser, so each change renders the plot **twice**: once with the stale value, then again when the new value arrives back from the client. Reactive defaults exist to avoid that second render. `dev-notes/reactive-defaults-repro.R` in the package source runs both approaches side by side with render counters if you want to see the difference. ### Passing defaults through `createModuleApp()` and `*App()` `defaults` is forwarded all the way through the app factory: ```{r defaults-app} plotthis_BoxPlotApp( data_list = list("iris" = example_iris), defaults = list( x.data = "Species", y.data = "Sepal.Length", add.points = TRUE, pt.size = 2 ) ) ``` Run on its bundled example data instead (no `data_list`), a `*App()` opens with the settings the module gallery (`moduleGalleryApp()`) shows it with, and any `defaults` you pass are layered over those: `plotthis_BoxPlotApp(defaults = list(stats.enabled = FALSE))` is the gallery's box plot without its significance brackets. The same `defaults` list is accepted by `createModuleApp()` directly: ```{r defaults-createModuleApp} app <- createModuleApp( inputs_ui_fn = plotthis_BoxPlotInputsUI, output_ui_fn = plotthis_BoxPlotOutputUI, server_fn = plotthis_BoxPlotServer, data_list = list("iris" = iris), defaults = list(x.data = "Species", y.data = "Sepal.Length") ) if (interactive()) runApp(app) ``` --- ## `hide.inputs`: hiding individual controls Pass a character vector of input IDs to `hide.inputs` on the **server** function. Those controls are hidden from the UI while their values are still initialised (from `defaults` if supplied, otherwise the built-in default) and passed to the plot on every render. ```{r hide-inputs} server <- function(input, output, session) { dittoViz_scatterPlotServer( "p", data = reactive(mtcars), hide.inputs = c("shape.by", "plot.order", "opacity") ) } ``` Hidden inputs reflow: the surrounding controls close the gap rather than leaving an empty space. This is handled automatically by the flexbox grid that `organize_inputs()` creates. ### Use case: app-level fixed parameters A common pattern is to fix a column mapping or aesthetic so that app users cannot change it, while still applying it to every plot: ```{r hide-fixed} server <- function(input, output, session) { dittoViz_scatterPlotServer( "p", data = reactive(mtcars), defaults = list(color.by = "cyl"), hide.inputs = "color.by" ) } ``` The colour mapping is always `cyl`, and the control for it never appears. This composes with reactive defaults: a hidden control whose default is a `reactive()` still drives the plot, because the value is resolved server-side rather than read back from the (now invisible) input. That is the cleanest way to lock a parameter to app state entirely. --- ## `hide.tabs`: hiding entire tab panels Modules organise their inputs into named tabs (e.g. `"Data"`, `"Points"`, `"Lines"`, `"Axes"`, `"Legend"`, `"Plotly"`). Pass a character vector of tab names to `hide.tabs` to remove whole groups at once. ```{r hide-tabs} server <- function(input, output, session) { dittoViz_scatterPlotServer( "p", data = reactive(mtcars), hide.tabs = c("Plotly", "Lines", "Trajectory") ) } ``` All inputs in a hidden tab are still initialised and active — they just aren't shown. This is useful when you want to keep the defaults for an entire feature group (e.g. plotly export settings) without exposing the controls to users. ### Available tab names Tab names are module-specific. Inspect the `inputs` list inside the relevant `*InputsUI()` source, or open `?InputsUI` and look for the tab headings described there. Common tabs across most modules include: | Tab | Contents | |-----|----------| | `"Data"` | Column selectors (x, y, color, split, etc.) | | `"Axes"` | Title font, gridlines, tick styling | | `"Legend"` | Legend visibility, font family and colour, title and text sizes | | `"Lines"` | Reference lines (h/v/ablines) | | `"Plotly"` | Download format, margins, drawn shape styling | | `"Facet"` | Facet rows/columns, scales, subplot spacing | --- ## Combining `defaults`, `hide.inputs`, and `hide.tabs` These three arguments compose freely. A typical production pattern is to set defaults for everything the user shouldn't touch, hide the individual controls that need fine-grained locking, and hide any whole tabs that are irrelevant for your use case: ```{r combined} ui <- fluidPage( sidebarLayout( sidebarPanel( plotthis_BoxPlotInputsUI( "b", example_rnaseq, defaults = list( x.data = "cell_type", y.data = "log2_cpm", group.by = "condition" ) ) ), mainPanel(plotthis_BoxPlotOutputUI("b")) ) ) server <- function(input, output, session) { plotthis_BoxPlotServer( "b", data = reactive(example_rnaseq), hide.inputs = c("group.by"), hide.tabs = c("Plotly", "Lines") ) } shinyApp(ui, server) ``` Here each cell type's boxes are always split by `"condition"`: `group.by` is set and then hidden, so users can change what is plotted but not the comparison. The entire `"Plotly"` and `"Lines"` tabs are removed because they aren't relevant to this app. --- ## Using `hide.inputs` in `createModuleApp()` and `*App()` Both accept `hide.inputs` and `hide.tabs`, forwarding them to the server if it supports those arguments: ```{r hide-in-app} plotthis_BoxPlotApp( data_list = list("iris" = example_iris), defaults = list(x.data = "Species", y.data = "Sepal.Length"), hide.inputs = "group.by", hide.tabs = "Plotly" ) ``` --- ## Hiding inputs dynamically in a custom module When building a wrapper module (see `vignette("custom-modules", package = "VizModules")`), you can hide and show inputs at runtime in response to other inputs using `shinyjs`: ```{r dynamic-hide} library(shinyjs) myModuleServer <- function(id, data_reactive) { moduleServer(id, function(input, output, session) { # Hide the 'size' input whenever a size.by column is chosen. isTRUE() # because input$size.by is NULL until the control first reports in. observe({ if (isTRUE(nzchar(input$size.by))) { shinyjs::hide(id = "size") } else { shinyjs::show(id = "size") } }) }) dittoViz_scatterPlotServer(id, data_reactive) } ``` Note that `shinyjs::hide()` / `shinyjs::show()` act on the input element itself. For the reflow behaviour (no empty gap), use the `hide_input()` / `show_input()` helpers instead, which target the wrapping cell in the flexbox grid: ```{r dynamic-hide-reflow} myModuleServer <- function(id, data_reactive) { moduleServer(id, function(input, output, session) { observe({ if (isTRUE(nzchar(input$size.by))) { VizModules::hide_input(session, "size") } else { VizModules::show_input(session, "size") } }) }) dittoViz_scatterPlotServer(id, data_reactive) } ``` If your server also takes `hide.inputs`, show only the ids the app did not list there (`show_input(session, setdiff("size", hide.inputs))`). Otherwise the next toggle un-hides an input the app meant to keep hidden. The built-in modules that toggle their own controls, such as `linePlot` with its error bar inputs, do this.