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:
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:
ElementaryTable
ggplot
grob
datatables
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.
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)
}
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)
}
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)
}
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
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)
}