Author: NEST CoreDev

# 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:

- Transforming the input data in this vignette.
- Transforming module output in this vignette.

## 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 (patchwork/ggplot)
`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. `patchwork`
4. `grob`
5. `datatables`

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 `patchwork`

Here’s an example to showcase how you can edit an output of class `patchwork`. Since `patchwork` objects support ggplot2-style addition, you can use `patchwork::plot_annotation()` and standard ggplot2 theme modifications.

    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
    })

    patchwork_title_decorator <- function(default_title = "I am a good decorator") {
      teal_transform_module(
        label = "Title",
        ui = function(id) shiny::textInput(shiny::NS(id, "title"), "Plot Title", value = default_title),
        server = function(id, data) {
          moduleServer(id, function(input, output, session) {
            reactive({
              data() |>
                within(
                  {
                    plot <- plot + patchwork::plot_annotation(title = plot_title)
                  },
                  plot_title = input$title
                )
            })
          })
        }
      )
    }

    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 = patchwork_title_decorator("I am a Scatterplot matrix")
          )
        )
      )
    )

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