Skip to contents

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.osprey 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_g_spiderplot plot ggplot
tm_g_butterfly plot grob / gtable
tm_g_waterfall plot grob / gtable
tm_g_swimlane plot grob / gtable
tm_g_patient_profile plot grob / ggplot
tm_g_ae_oview plot grob
tm_g_ae_sub plot grob
tm_g_events_term_id plot grob
tm_g_heat_bygrade plot grob / gtable

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

  1. ggplot
  2. grob
  3. gtable

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.osprey)

data <- within(teal_data(), {
  require(nestcolor)
  ADSL <- rADSL
  ADTR <- rADTR
})

join_keys(data) <- default_cdisc_join_keys[names(data)]

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_g_spiderplot(
      label = "Spiderplot",
      dataname = "ADTR",
      paramcd = variables(
        choices = is_categorical(),
        selected = "PARAMCD"
      ),
      x_var = variables(
        choices = dplyr::where(is.numeric),
        selected = 1L
      ),
      y_var = variables(
        choices = c("PCHG", "CHG", "AVAL"),
        selected = "PCHG"
      ),
      marker_var = variables(
        choices = c("SEX", "RACE", "USUBJID"),
        selected = "SEX"
      ),
      line_colorby_var = variables(
        choices = c("SEX", "USUBJID", "RACE"),
        selected = "SEX"
      ),
      xfacet_var = variables(
        choices = c("SEX", "ARM"),
        selected = "SEX"
      ),
      yfacet_var = variables(
        choices = c("SEX", "ARM"),
        selected = "ARM"
      ),
      decorators = list(
        plot = ggplot_caption_decorator("I am a ggplot")
      )
    )
  )
)

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.osprey)

data <- within(teal_data(), {
  ADSL <- rADSL
  ADTR <- rADTR
})

join_keys(data) <- default_cdisc_join_keys[names(data)]

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_ae_sub(
      label = "AE subview",
      dataname = "ADTR",
      arm_var = variables(
        choices = is_categorical(),
        selected = "ACTARMCD"
      ),
      group_var = variables(
        choices = is_categorical(),
        selected = c("SEX", "REGION1", "RACE")
      ),
      decorators = list(
        plot = grob_caption_decorator("I am a grob")
      )
    )
  )
)

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

Decorating gtable

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

library(teal.osprey)
library("dplyr")
data <- within(teal_data(), {
  library("dplyr")
  ADSL <- rADSL %>%
    mutate(TRTDURD = as.integer(TRTEDTM - TRTSDTM) + 1) %>%
    filter(STRATA1 == "A" & ARMCD == "ARM A")
  ADRS <- rADRS %>%
    filter(PARAMCD == "LSTASDI" & DCSREAS == "Death") %>%
    mutate(AVALC = DCSREAS, ADY = EOSDY) %>%
    rbind(
      rADRS %>%
        filter(PARAMCD == "OVRINV" & AVALC != "NE")
    ) %>%
    arrange(USUBJID)
})

join_keys(data) <- default_cdisc_join_keys[names(data)]

gtable_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 <- gtable::gtable_add_grob(
                  plot,
                  footnote_grob,
                  t = 1,
                  l = 1,
                  clip = "on"
                )
              },
              footnote = input$footnote
            )
        })
      })
    }
  )
}

app <- init(
  data = data,
  modules = modules(
    tm_g_swimlane(
      label = "Swimlane Plot",
      dataname = "ADRS",
      bar_var = variables(
        choices = c("TRTDURD", "EOSDY"),
        selected = "TRTDURD"
      ),
      bar_color_var = variables(
        choices = c("EOSSTT", "ARM", "ARMCD", "ACTARM", "ACTARMCD", "SEX"),
        selected = "EOSSTT"
      ),
      decorators = list(
        plot = gtable_caption_decorator("I am a gtable")
      )
    )
  )
)

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