Decorate Module Output

NEST CoreDev

Introduction

The outputs produced by teal modules, like graphs or tables, are created by the module developer and look a certain way. It is hard to design an output that will satisfy every possible user, so the form of the output should be considered a default value that can be customized. Here we describe the concept of decoration, enabling the app developer to tailor outputs to their specific requirements without rewriting the original module code.

The decoration process is build upon transformation procedures, introduced in teal. While transformators are meant to edit module’s input, decorators are meant to adjust the module’s output. To distinguish the difference, modules in teal.modules.general have 2 separate parameters: transformators and decorators.

To get a complete understanding refer the following vignettes:

Outputs that can be decorated

It is important to note which output objects from a given module can be decorated. The module function documentation’s Decorating Module section has this information.

You can also refer the table shown below to know which module outputs can be decorated.

Module Output (Class)
tm_a_pca elbow_plot (ggplot), circle_plot (ggplot), biplot (ggplot), eigenvector_plot (ggplot)
tm_a_regression plot (ggplot)
tm_g_association plot (grob)
tm_g_bivariate plot (ggplot)
tm_g_distribution histogram_plot (ggplot), qq_plot (ggplot), summary_table (datatables), test_table (datatables)
tm_g_response plot (ggplot)
tm_g_scatterplot plot (ggplot)
tm_g_scatterplotmatrix plot (trellis)
tm_missing_data summary_plot (grob), combination_plot (grob), by_subject_plot (ggplot), table (datatables)
tm_outliers box_plot (ggplot), density_plot (ggplot), cumulative_plot (ggplot), table (datatables)
tm_t_crosstable table (ElementaryTable)

Also, note that there are five different types of objects that can be decorated:

  1. ElementaryTable
  2. ggplot
  3. grob
  4. datatables
  5. trellis

Tip: A general tip before trying to decorate the output from the module is to copy the reproducible code and running them in a separate R session to quickly iterate the decoration you want.

Decorating ElementaryTable

Here’s an example to showcase how you can edit an output of class ElementaryTable. rtables modifiers like rtables::insert_rrow can be applied to modify this object.

library(teal.modules.general)

data <- teal_data(join_keys = default_cdisc_join_keys[c("ADSL", "ADRS")])
data <- within(data, {
  require(nestcolor)
  ADSL <- rADSL
})

insert_rrow_decorator <- function(default_caption = "I am a good new row") {
  teal_transform_module(
    label = "New row",
    ui = function(id) {
      shiny::textInput(shiny::NS(id, "new_row"), "New row", value = default_caption)
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          data() |>
            within(
              {
                table <- rtables::insert_rrow(table, rtables::rrow(new_row))
              },
              new_row = input$new_row
            )
        })
      })
    }
  )
}

app <- init(
  data = data,
  modules = modules(
    tm_t_crosstable(
      label = "Cross Table",
      x = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          choices = variable_choices(data[["ADSL"]], subset = function(data) {
            idx <- !vapply(data, inherits, logical(1), c("Date", "POSIXct", "POSIXlt"))
            names(data)[idx]
          }),
          selected = "COUNTRY",
          multiple = TRUE,
          ordered = TRUE
        )
      ),
      y = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          choices = variable_choices(data[["ADSL"]], subset = function(data) {
            idx <- vapply(data, is.factor, logical(1))
            names(data)[idx]
          }),
          selected = "SEX"
        )
      ),
      decorators = list(
        table = insert_rrow_decorator()
      )
    )
  )
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}

Decorating ggplot

Here’s an example to showcase how you can edit an output of class ggplot. You can extend them using ggplot2 functions.

library(teal.modules.general)

data <- teal_data(join_keys = default_cdisc_join_keys[c("ADSL", "ADRS")])
data <- within(data, {
  require(nestcolor)
  ADSL <- rADSL
})

ggplot_caption_decorator <- function(default_caption = "I am a good decorator") {
  teal_transform_module(
    label = "Caption",
    ui = function(id) {
      shiny::textInput(shiny::NS(id, "footnote"), "Footnote", value = default_caption)
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          data() |>
            within(
              {
                plot <- plot + ggplot2::labs(caption = footnote)
              },
              footnote = input$footnote
            )
        })
      })
    }
  )
}

app <- init(
  data = data,
  modules = modules(
    tm_a_regression(
      label = "Regression",
      response = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          label = "Select variable:",
          choices = "BMRKR1",
          selected = "BMRKR1",
          multiple = FALSE,
          fixed = TRUE
        )
      ),
      regressor = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          label = "Select variables:",
          choices = variable_choices(data[["ADSL"]], c("AGE", "SEX", "RACE")),
          selected = "AGE",
          multiple = TRUE,
          fixed = FALSE
        )
      ),
      decorators = list(
        plot = ggplot_caption_decorator("I am a Regression")
      )
    )
  )
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}

Decorating grob

Here’s an example to showcase how you can edit an output of class grob. You can extend them using grid and gridExtra functions.

library(teal.modules.general)

data <- teal_data(join_keys = default_cdisc_join_keys[c("ADSL", "ADRS")])
data <- within(data, {
  ADSL <- rADSL
})

grob_caption_decorator <- function(default_caption = "I am a good decorator") {
  teal_transform_module(
    label = "Caption",
    ui = function(id) {
      shiny::textInput(shiny::NS(id, "footnote"), "Footnote", value = default_caption)
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          data() |>
            within(
              {
                footnote_grob <- grid::textGrob(
                  footnote,
                  x = 0, hjust = 0,
                  gp = grid::gpar(fontsize = 10, fontface = "italic", col = "gray50")
                )
                plot <- gridExtra::arrangeGrob(
                  plot,
                  footnote_grob,
                  ncol = 1,
                  heights = grid::unit.c(
                    grid::unit(1, "npc") - grid::unit(1, "lines"), grid::unit(1, "lines")
                  )
                )
              },
              footnote = input$footnote
            )
        })
      })
    }
  )
}

app <- init(
  data = data,
  modules = modules(
    tm_g_association(
      ref = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          choices = variable_choices(
            data[["ADSL"]],
            c("SEX", "RACE", "COUNTRY", "ARM", "STRATA1", "STRATA2", "ITTFL", "BMRKR2")
          ),
          selected = "RACE"
        )
      ),
      vars = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          choices = variable_choices(
            data[["ADSL"]],
            c("SEX", "RACE", "COUNTRY", "ARM", "STRATA1", "STRATA2", "ITTFL", "BMRKR2")
          ),
          selected = "BMRKR2",
          multiple = TRUE
        )
      ),
      decorators = list(
        plot = grob_caption_decorator("I am a Association")
      )
    )
  )
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}

Decorating datatables

Here’s an example to showcase how you can edit an output of class datatables. Please refer the helper functions of the DT package to learn more about extending the datatables objects.

library(teal.modules.general)

data <- teal_data(join_keys = default_cdisc_join_keys[c("ADSL", "ADRS")])
data <- within(data, {
  require(nestcolor)
  ADSL <- rADSL
})
fact_vars_adsl <- names(Filter(isTRUE, sapply(data[["ADSL"]], is.factor)))
vars <- choices_selected(variable_choices(data[["ADSL"]], fact_vars_adsl))

dt_table_decorator <- function(color1 = "pink", color2 = "lightblue") {
  teal_transform_module(
    label = "Table color",
    ui = function(id) {
      selectInput(
        NS(id, "color"),
        "Table Color",
        choices = c("white", color1, color2),
        selected = "Default"
      )
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          data() |> within(
            {
              summary_table <- DT::formatStyle(
                summary_table,
                columns = attr(summary_table$x, "colnames")[-1],
                target = "row",
                backgroundColor = color
              )
            },
            color = input$color
          )
        })
      })
    }
  )
}

app <- init(
  data = data,
  modules = modules(
    tm_g_distribution(
      dist_var = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          choices = variable_choices(data[["ADSL"]], c("AGE", "BMRKR1")),
          selected = "BMRKR1",
          multiple = FALSE,
          fixed = FALSE
        )
      ),
      strata_var = data_extract_spec(
        dataname = "ADSL",
        filter = filter_spec(
          vars = vars,
          multiple = TRUE
        )
      ),
      group_var = data_extract_spec(
        dataname = "ADSL",
        filter = filter_spec(
          vars = vars,
          multiple = TRUE
        )
      ),
      decorators = list(
        summary_table = dt_table_decorator()
      )
    )
  )
)
## Initializing tm_g_distribution
if (interactive()) {
  shinyApp(app$ui, app$server)
}

Decorating trellis

Here’s an example to showcase how you can edit an output of class trellis. rtables modifiers like rtables::insert_rrow can be applied to modify this object.

library(teal.modules.general)

data <- teal_data(join_keys = default_cdisc_join_keys[c("ADSL", "ADRS")])
data <- within(data, {
  require(nestcolor)
  ADSL <- rADSL
  ADRS <- rADRS
})

trellis_subtitle_decorator <- function(default_caption = "I am a good decorator") {
  teal_transform_module(
    label = "Caption",
    ui = function(id) shiny::textInput(shiny::NS(id, "footnote"), "Footnote", value = default_caption),
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        reactive({
          data() |>
            within(
              {
                plot <- update(plot, sub = footnote)
              },
              footnote = input$footnote
            )
        })
      })
    }
  )
}

app <- init(
  data = data,
  modules = modules(
    tm_g_scatterplotmatrix(
      label = "Scatterplot matrix",
      variables = list(
        data_extract_spec(
          dataname = "ADSL",
          select = select_spec(
            choices = variable_choices(data[["ADSL"]]),
            selected = c("AGE", "RACE", "SEX"),
            multiple = TRUE,
            ordered = TRUE
          )
        ),
        data_extract_spec(
          dataname = "ADRS",
          filter = filter_spec(
            label = "Select endpoints:",
            vars = c("PARAMCD", "AVISIT"),
            choices = value_choices(data[["ADRS"]], c("PARAMCD", "AVISIT"), c("PARAM", "AVISIT")),
            selected = "INVET - END OF INDUCTION",
            multiple = TRUE
          ),
          select = select_spec(
            choices = variable_choices(data[["ADRS"]]),
            selected = c("AGE", "AVAL", "ADY"),
            multiple = TRUE,
            ordered = TRUE
          )
        )
      ),
      decorators = list(
        plot = trellis_subtitle_decorator("I am a Scatterplot matrix")
      )
    )
  )
)

if (interactive()) {
  shinyApp(app$ui, app$server)
}