Skip to contents

This checklist walks through how to add a new plotting module to VizModules so it matches the package’s organization, documentation, and testing standards.

Quick Checklist

    • For dittoViz functions: use dittoViz_<PlotName> (e.g., dittoViz_scatterPlot for dittoViz::scatterPlot)
    • For plotthis functions: use plotthis_<PlotName> (e.g., plotthis_AreaPlot for plotthis::AreaPlot)
    • For custom/standalone functions: use the plot name directly (e.g., linePlot, piePlot)
    • @section Plot parameters not implemented or with altered functionality: - List inputs not exposed and why
    • @section Plot parameters and defaults: - Document all exposed parameters with UI labels and defaults
    • @section Plot parameters implementing new functionality: - Document any new or plotly-specific controls (axes, ticks, reference lines, etc)

Naming & Organization

    • dittoViz wrappers: dittoViz_<PlotName>_module_ui.R (e.g., dittoViz_scatterPlot_module_ui.R)
    • plotthis wrappers: plotthis_<PlotName>_module_ui.R (e.g., plotthis_AreaPlot_module_ui.R)
    • Custom modules: <plotName>_module_ui.R (e.g., linePlot_module_ui.R)

Documentation Standards

1. @section Plot parameters not implemented or with altered functionality:

List all parameters from the base plot function that are not exposed via UI inputs, with explanations:

#' @section Plot parameters not implemented or with altered functionality:
#' The following [plotthis::AreaPlot()] parameters are not available via UI inputs:
#' \itemize{
#'   \item \code{xlab} - X-axis label (plotly allows interactive editing)
#'   \item \code{ylab} - Y-axis label (plotly allows interactive editing)
#'   \item \code{title} - Plot title (plotly allows interactive editing)
#'   \item \code{subtitle} - Plot subtitle (not supported in plotly)
#'   \item \code{legend.position} - Legend positioning (plotly allows interactive repositioning)
#'   \item \code{split_by} - Split variable (returns a patchwork object, not supported in plotly)
#'   \item \code{palette} - Managed internally via the palette selection UI
#' }

2. @section Plot parameters and defaults:

Document all parameters that are exposed, listing their UI label and default value:

#' @section Plot parameters and defaults:
#' The following [plotthis::AreaPlot()] parameters can be accessed via UI inputs and/or the \code{defaults} argument:
#' \itemize{
#'   \item \code{x} - X-axis variable (UI: "X values", default: 2nd categorical variable)
#'   \item \code{y} - Y-axis variable (UI: "Y values", default: 2nd numeric variable)
#'   \item \code{group_by} - Grouping variable (UI: "Group by", default: 3rd categorical variable or "")
#'   \item \code{facet_by} - Faceting variable (UI: "Facet by", default: "")
#'   \item \code{theme} - ggplot2 theme (UI: "Theme", default: "theme_this")
#'   \item \code{alpha} - Area fill transparency (UI: "Alpha", default: 1)
#' }

3. @section Plot parameters implementing new functionality:

Document all module-specific parameters (plotly controls, reference lines, etc.):

#' The following parameters implementing new functionality or controlling plotly-specific features are also available:
#' \itemize{
#'   \item \code{axis.font.size} - Axis title font size (UI: "Axis font size", default: 18)
#'   \item \code{axis.showline} - Show axis border lines (UI: "Show axis lines", default: TRUE)
#'   \item \code{axis.tickfont.size} - Size of tick labels (UI: "Tick label size", default: 12)
#'   \item \code{hline.intercepts} - Y-coordinates for horizontal reference lines (UI: "Y-intercepts", default: "")
#'   \item \code{hline.colors} - Colors for horizontal lines, comma-separated (UI: "Colors", default: "#000000")
#'   \item \code{hline.linetypes} - Line types for horizontal lines, comma-separated (UI: "Line types", default: "dashed")
#'   \item \code{vline.intercepts} - X-coordinates for vertical reference lines (UI: "X-intercepts", default: "")
#'   \item \code{abline.slopes} - Slopes for diagonal reference lines (UI: "Slopes", default: "")
#' }

Note: Reference line parameters (hline.*, vline.*, abline.*) accept comma-separated values to control each line individually.

Functionality & Non-Exposed Inputs

Example App Requirement

myPlotApp <- function(data_list = NULL) {
    if (is.null(data_list)) {
        data_list <- list("example" = my_default_data)
    }
    createModuleApp(
        inputs_ui_fn = myPlotInputsUI,
        output_ui_fn = myPlotOutputUI,
        server_fn    = myPlotServer,
        data_list    = data_list,
        title        = "Modular myPlots"
    )
}

Testing Requirements

Implementing a New Plotting Function (e.g., piePlot)

Integrating Statistical Testing (Stats Tab)

Modules for categorical-vs-numeric plots (box, violin, etc.) can include an optional Stats tab that provides pairwise statistical testing with plotly bracket annotations. If your new module supports grouped comparisons along a categorical x-axis, follow this pattern:

UI

Server

Key helpers (all in R/stat_helper.R)

Function Purpose
.compute_pairwise_stats() Run pairwise or omnibus tests with p-value adjustment
.create_stat_annotations() Convert stats to plotly shapes/annotations with bracket packing
.apply_stat_annotations() Append shapes/annotations to the plotly figure and adjust y-axes
.generate_pair_strings() Build "A vs B" strings for the comparison selector
.parse_pair_strings() Convert selected pair strings back to list of length-2 vectors
.write_stats_csv() Write stats CSV with metadata comment header

See the plotthis_BoxPlotServer, plotthis_ViolinPlotServer, or dittoViz_yPlotServer implementations for complete integration examples.

Review Before Submitting

Style Guide

Following a consistent style makes the package easier to read, maintain, and extend. Apply these conventions to every new module.

Input Labels

  • Capitalize the first word of every input label: "Group By", not "group by".
  • Be concise — prefer short, scannable labels over long descriptions. Move detail into a tipify tooltip instead.
  • Avoid redundant words. "Color" is better than "Select a Color".
  • Match plotthis/dittoViz parameter names loosely, so users can cross-reference the upstream docs. E.g., label the group_by input "Group By".

Tooltips with tipify

Wrap any non-obvious input in shinyBS::tipify() to show a tooltip on hover. This keeps labels concise while still informing the user.

Apply tipify when:

  • The input’s purpose is not immediately clear from its label alone.
  • The input accepts a specific format that users might not guess (e.g., comma-separated values, index positions for categorical axes).
  • The input has a non-trivial effect on the plot (e.g., stat correction methods, bracket inset).

Standard pattern — always use placement = "top" and options = list(container = "body") so tooltips render correctly inside sidebar panels:

tipify(
    textInput(ns("hline.intercepts"), "Y-intercepts",
        placeholder = "e.g. 2, -2",
        value = .get_default(defaults, "hline.intercepts", "")
    ),
    paste(
        "For categorical or factor axes, enter the index (position) of the",
        "category rather than its name."
    ),
    placement = "top", options = list(container = "body")
)

Inputs that are self-explanatory from their label (e.g., "Plot Title", "X-axis Variable") do not need a tooltip.

Reuse Uniform Input Helpers

In time, these helpers will be further formalized and exported, but they can be used with the VizModules::: prefix in the meantime.

Before writing custom inputs, check whether a uniform helper already covers your needs:

Helper Provides
.uniform_lines_inputs_ui() Horizontal, vertical, and diagonal reference line controls
.uniform_axes_inputs_ui() Font, axis border, gridline, tick, and facet styling
.uniform_stats_inputs_ui() Pairwise statistical testing and bracket annotation controls
.uniform_plotly_inputs_ui() Download buttons, margins, subplot spacing, and draw-shape styling

Pass ns and a defaults list to each helper. Use the include.* arguments to opt in to optional groups (e.g., include.fit.lines = TRUE for scatter plots, include.rotate = TRUE for bar plots).

Using the uniform helpers ensures that shared inputs behave identically across every module and that future changes to those helpers propagate automatically.

Imports: @importFrom Over ::

  • Always use @importFrom pkg fun in the roxygen header of any file that calls an external function, then call the function directly (fun()) in the body.
  • Avoid pkg::fun() calls in module code.

The only exception is a one-off call in an @examples block or vignette where the full qualified name aids readability.

Additional Conventions

  • Use 4-space indentation and keep lines to 120 characters max (enforced by .lintr).
  • Avoid sapply() — use vapply() or lapply() with explicit types instead.
  • Do not edit NAMESPACE manually; always regenerate with devtools::document().

Sanitizing User-Provided Expressions

Never use eval(str2expression()) or eval(parse()) on raw user input. If a Shiny app is deployed publicly, this allows arbitrary code execution on the server (e.g., system("rm -rf /")). VizModules provides three exported helper functions for safely handling user-typed expressions. Use them whenever your module accepts free-text input that will be evaluated or passed to a plotting function.

safe_eval_filter(expr_text, data)

Use when a module evaluates a user-typed filter expression directly to produce a logical vector for row subsetting. The expression is parsed, its AST is walked to ensure only allowed operations are present (comparisons, logical operators, column references, and literals), and then it is evaluated in a restricted environment containing only the data frame’s columns.

# In a module server — filtering rows by a textInput:
rows.use = safe_eval_filter(isolate_fn(input$rows.use), data())

Returns a logical vector (same length as nrow(data)), or NULL if the input is empty, unparseable, or contains disallowed operations.

validate_expression(expr_text, col_names)

Use when a module passes a user-typed expression string through to a downstream plotting function that will evaluate it internally (e.g., plotthis::BoxPlot(highlight = ...)). The string is validated but not executed.

# In a module server — passing a highlight expression to plotthis:
highlight <- validate_expression(isolate_fn(input$highlight), names(data()))

Returns the original string if safe, or NULL.

safe_resolve_adj_fxn(fn_name)

Use when a module resolves a function name from a dropdown or text input into an actual function reference (e.g., for x.adj.fxn, y.adj.fxn). Only function names in the allowed list ("log2", "log", "log10", "neg_log10", "log1p", "as.factor", "abs", "sqrt") are accepted.

# In a module server — resolving an adjustment function:
x.adj.fxn = safe_resolve_adj_fxn(isolate_fn(input$x.adj.fxn))

Returns the function, or NULL if the name is empty or not in the allowed list.

What counts as “allowed”?

All three helpers share the same whitelist of safe AST nodes:

  • Comparisons: <, >, <=, >=, ==, !=
  • Logical operators: &, &&, |, ||, !
  • Utilities: %in%, c(), is.na(), is.null()
  • Arithmetic: -, +, *, /, :, %%
  • Grouping: ()
  • Column names from the data
  • Literals: numbers, strings, TRUE, FALSE, NA, NULL, Inf, NaN

Anything outside this list (including function calls like system(), file.remove(), library(), etc.) is rejected and a warning is issued.