Custom ggplot2 arguments module

Maciej Nasiński

This vignette will guide you through implementation of custom ggplot2::labs and ggplot2::theme for ggplot2 graphics based modules. We will enable 2 ways of updating ggplot2::labs and ggplot2::theme by the end users. The ggplot2 specification could be updated with the teal.ggplot2_args options variable or a ggplot2_args argument in a tm_g_* module. We still take into account default specification set up by the module creator in the server function, which has the lowest priority.

The implementation should consist of 5 steps:

  1. Adding a ggplot2_args argument to the tm_g_* function and then its server function. The default should be set to the ggplot2_args(labs = list(), theme = list()) function for single plot. and list(default = ggplot2_args(labs = list(), theme = list())) multi-plot modules.
  2. Adding a validation (e.g. stopifnot or checkmate) of the ggplot2_args argument to the tm_* function. The validation is more complex for multi-plot modules, where the ggplot2_args could be a list. The module creator has to provide a list of plots names, which should be validated at this step and added to the param field in roxygen2. For multi-plot modules the step if (is_ggplot2_args) ggplot2_args <- list(default = ggplot2_args) is recommended.
  3. Aggregating and reducing all ggplot2_args sources with resolve_ggplot2_args().
  4. Usage of the parse_ggplot2_args() function which will parse inputs to list of expressions.
  5. Adding the created expression to the last chunk of a plot. Reduce(function(x, y) call("+", x, y), list(...) function could be helpful at this step.

The resolve_ggplot2_args() function picks the first non NULL value for each argument, checking in order:

  1. ggplot2_args argument provided by the end user. For multi-plot case, per plot (user_plot) and then default (user_default) setup.
  2. Global R variable (options), teal.ggplot2_args.
  3. module_plot which is a developer setup.

Additional topics

When a more complex ggplot2 object has to be used inside the teal.widgets::ggplot2_args function, then a base::quote function would prevent an object expansion in Show R Code. For example the ggplot2::element_text function returns a complex object, then we should use code like teal.widgets::ggplot2_args(theme = list(plot.title = quote(ggplot2::element_text(size = 20)))) to prevent Show R Code expansion.

If you get a promise already under evaluation: recursive default argument reference or earlier problems? error, then probably your function argument has the same name as a function which is an input for it. To solve the problem please use :: to prefix it directly to a specific package, like new_fun <- function(ggplot2_args = teal.widgets::ggplot2_args()).

Loading libraries and data

library(shiny)
library(ggplot2)
library(teal.widgets)

options("teal.ggplot2_args" = teal.widgets::ggplot2_args(labs = list(caption = "Caption from options")))

user_ggplot2_args <- list(
  default = ggplot2_args(
    labs = list(title = "User default title"),
    theme = list(legend.position = "right", legend.direction = "vertical")
  ),
  plot1 = ggplot2_args(
    labs = list(title = "User title"),
    theme = list(legend.position = "right", legend.direction = "vertical")
  )
)

app <- shinyApp(
  ui = fluidPage(
    shinyjs::useShinyjs(),
    div(plotOutput("plot1"))
  ),
  server = function(input, output, session) {
    dev_ggplot2_args <- ggplot2_args(
      labs = list(subtitle = "Dev substitle"),
      theme = list(legend.position = "none")
    )

    f_ggplot2_expr <- parse_ggplot2_args(
      resolve_ggplot2_args(
        user_plot = user_ggplot2_args$plot1,
        user_default = user_ggplot2_args$default,
        module_plot = dev_ggplot2_args
      )
    )

    plot_expr <- substitute(
      expr = {
        gg <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color = Species)) +
          geom_point() +
          ggplot_expr_labs +
          ggplot_expr_theme
        print(gg)
      },
      env = list(ggplot_expr_labs = f_ggplot2_expr$labs, ggplot_expr_theme = f_ggplot2_expr$theme)
    )
    print(plot_expr)
    output$plot1 <- renderPlot(eval(plot_expr))
  }
)
shinyApp(app$ui, app$server)