Skip to content

Commit f014f3e

Browse files
authored
feat: refactored outliers to allow for decorators (#788)
1 parent de6f56c commit f014f3e

File tree

3 files changed

+205
-10
lines changed

3 files changed

+205
-10
lines changed

R/tm_outliers.R

Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,62 @@
1111
#' Specifies variable(s) to be analyzed for outliers.
1212
#' @param categorical_var (`data_extract_spec` or `list` of multiple `data_extract_spec`) optional,
1313
#' specifies the categorical variable(s) to split the selected outlier variables on.
14+
#' @param table_decorator (`teal_transform_module`, `language` or `function`) optional,
15+
#' decorator for the table.
16+
#' @param boxplot_decorator (`teal_transform_module`, `language` or `function`) optional,
17+
#' decorator for the box plot.
18+
#' @param violin_decorator (`teal_transform_module`, `language` or `function`) optional,
19+
#' decorator for the violing plot.
20+
#' @param density_decorator (`teal_transform_module`, `language` or `function`) optional,
21+
#' decorator for the density plot.
22+
#' @param cum_dist_decorator (`teal_transform_module`, `language` or `function`) optional,
23+
#' decorator for the cumulative distribution plot.
1424
#'
1525
#' @templateVar ggnames "Boxplot","Density Plot","Cumulative Distribution Plot"
1626
#' @template ggplot2_args_multi
1727
#'
1828
#' @inherit shared_params return
1929
#'
30+
#' @section Decorating the tables and plots:
31+
#' The act of decoration means to modify the tables and plots output by this module.
32+
#' The module lets app developers do it by allowing them to execute arbitrary R code
33+
#' that modifies the objects displayed by the module.
34+
#'
35+
#' The decorating parameters accept one of the three types of objects as decorators:
36+
#' * `teal_transform_module`
37+
#' * `language`
38+
#' * `function` of the output object.
39+
#'
40+
#' The module will execute the code contained in either of the objects just before
41+
#' rendering the outputs in the application. This lets app developers modify
42+
#' features like: titles, labels, sizes, limits, etc. of the rendered tables
43+
#' and plots.
44+
#'
45+
#' The app developer can pass either of the types. See examples for the proper
46+
#' use of each of the type of the decorator.
47+
#'
48+
#' IMPORTANT
49+
#' The `language` and `teal_transform_module` decorators are required by the module
50+
#' to overwrite the binding of the output, otherwise the effect of the decorator
51+
#' is not going to be visible. E.g.:
52+
#'
53+
#' ```{r}
54+
#' # The module uses `g` variable for the plot
55+
#'
56+
#' # Will work
57+
#' lang_dec <- quote({
58+
#' g <- g + ggplot2::ggtitle("A new title")
59+
#' })
60+
#'
61+
#' # Will not work because the decorater overwrites `plot` instead of `g`
62+
#' lang_dec <- quote({
63+
#' plot <- g + ggplot2::ggtitle("A new title")
64+
#' })
65+
#' ```
66+
#'
67+
#' The app developer can discover the bindings used for the outputs by inspecting
68+
#' the R code generated by the module.
69+
#'
2070
#' @examples
2171
#' library(teal.widgets)
2272
#'
@@ -120,6 +170,27 @@
120170
#' shinyApp(app$ui, app$server)
121171
#' }
122172
#'
173+
#' # Decorators
174+
#' function_decorator <- function(p) {
175+
#' p <- p + ggplot2::ggtitle("A new title")
176+
#' }
177+
#'
178+
#' quote_decorator <- quote({
179+
#' g <- g + ggplot2::ggtitle("A new title")
180+
#' })
181+
#'
182+
#' module_decorator <- teal_transform_module(
183+
#' ui = function(id) NULL,
184+
#' srv = function(id, data) {
185+
#' within(
186+
#' data,
187+
#' {
188+
#' g <- g + ggplot2::ggtitle("A new title")
189+
#' }
190+
#' )
191+
#' }
192+
#' )
193+
#'
123194
#' @export
124195
#'
125196
tm_outliers <- function(label = "Outliers Module",
@@ -130,13 +201,24 @@ tm_outliers <- function(label = "Outliers Module",
130201
plot_height = c(600, 200, 2000),
131202
plot_width = NULL,
132203
pre_output = NULL,
133-
post_output = NULL) {
204+
post_output = NULL,
205+
table_decorator = teal_transform_module(),
206+
boxplot_decorator = teal_transform_module(),
207+
violin_decorator = teal_transform_module(),
208+
density_decorator = teal_transform_module(),
209+
cum_dist_decorator = teal_transform_module()) {
134210
message("Initializing tm_outliers")
135211

136212
# Normalize the parameters
137213
if (inherits(outlier_var, "data_extract_spec")) outlier_var <- list(outlier_var)
138214
if (inherits(categorical_var, "data_extract_spec")) categorical_var <- list(categorical_var)
139215
if (inherits(ggplot2_args, "ggplot2_args")) ggplot2_args <- list(default = ggplot2_args)
216+
217+
table_decorator <- decorate_teal_data(table_decorator, output_name = "summary_table")
218+
boxplot_decorator <- decorate_teal_data(boxplot_decorator, output_name = "g")
219+
violin_decorator <- decorate_teal_data(violin_decorator, output_name = "g")
220+
density_decorator <- decorate_teal_data(density_decorator, output_name = "g")
221+
cum_dist_decorator <- decorate_teal_data(cum_dist_decorator, output_name = "g")
140222

141223
# Start of assertions
142224
checkmate::assert_string(label)
@@ -177,15 +259,18 @@ tm_outliers <- function(label = "Outliers Module",
177259
categorical_var = categorical_var
178260
)
179261

262+
180263
ans <- module(
181264
label = label,
182265
server = srv_outliers,
183266
server_args = c(
184267
data_extract_list,
185-
list(plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args)
268+
list(
269+
plot_height = plot_height, plot_width = plot_width, ggplot2_args = ggplot2_args,
270+
decorators = list(table = table_decorator, boxplot = boxplot_decorator, violin = violin_decorator, density = density_decorator, cum_dist = cum_dist_decorator))
186271
),
187272
ui = ui_outliers,
188-
ui_args = args,
273+
ui_args = c(args),
189274
datanames = teal.transform::get_extract_datanames(data_extract_list)
190275
)
191276
attr(ans, "teal_bookmarkable") <- TRUE
@@ -198,24 +283,36 @@ ui_outliers <- function(id, ...) {
198283
ns <- NS(id)
199284
is_single_dataset_value <- teal.transform::is_single_dataset(args$outlier_var, args$categorical_var)
200285

286+
201287
teal.widgets::standard_layout(
202288
output = teal.widgets::white_small_well(
203289
uiOutput(ns("total_outliers")),
204290
DT::dataTableOutput(ns("summary_table")),
291+
ui_teal_data(ns("table_decorator"), args$table_decorator),
205292
uiOutput(ns("total_missing")),
206293
tags$br(), tags$hr(),
207294
tabsetPanel(
208295
id = ns("tabs"),
209296
tabPanel(
210297
"Boxplot",
298+
conditionalPanel(
299+
condition = sprintf("input['%s'] == 'Box plot'", ns("boxplot_alts")),
300+
ui_teal_data(ns("boxplot_decorator"), args$boxplot_decorator)
301+
),
302+
conditionalPanel(
303+
condition = sprintf("input['%s'] == 'Violin plot'", ns("boxplot_alts")),
304+
ui_teal_data(ns("violin_decorator"), args$violin_decorator)
305+
),
211306
teal.widgets::plot_with_settings_ui(id = ns("box_plot"))
212307
),
213308
tabPanel(
214309
"Density Plot",
310+
ui_teal_data(ns("density_decorator"), args$density_decorator),
215311
teal.widgets::plot_with_settings_ui(id = ns("density_plot"))
216312
),
217313
tabPanel(
218314
"Cumulative Distribution Plot",
315+
ui_teal_data(ns("cum_dist_decorator"), args$cum_dist_decorator),
219316
teal.widgets::plot_with_settings_ui(id = ns("cum_density_plot"))
220317
)
221318
),
@@ -326,7 +423,7 @@ ui_outliers <- function(id, ...) {
326423

327424
# Server function for the outliers module
328425
srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var,
329-
categorical_var, plot_height, plot_width, ggplot2_args) {
426+
categorical_var, plot_height, plot_width, ggplot2_args, decorators) {
330427
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter")
331428
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI")
332429
checkmate::assert_class(data, "reactive")
@@ -669,13 +766,14 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var,
669766
qenv
670767
})
671768

769+
decorated_table_q <- srv_teal_data(id = "table_decorator", data = common_code_q, data_module = decorators$table, modules = module())
672770
output$summary_table <- DT::renderDataTable(
673771
expr = {
674772
if (iv_r()$is_valid()) {
675773
categorical_var <- as.vector(merged$anl_input_r()$columns_source$categorical_var)
676774
if (!is.null(categorical_var)) {
677775
DT::datatable(
678-
common_code_q()[["summary_table"]],
776+
decorated_table_q()[["summary_table"]],
679777
options = list(
680778
dom = "t",
681779
autoWidth = TRUE,
@@ -1024,18 +1122,30 @@ srv_outliers <- function(id, data, reporter, filter_panel_api, outlier_var,
10241122
}
10251123
})
10261124

1027-
boxplot_r <- reactive({
1125+
undecorated_boxplot_r <- reactive({
10281126
teal::validate_inputs(iv_r())
10291127
boxplot_q()[["g"]]
10301128
})
1031-
density_plot_r <- reactive({
1129+
decorated_boxplot_r <- srv_teal_data(id = "boxplot_decorator", data = undecorated_boxplot_r, data_module = decorators$boxplot, modules = module())
1130+
decorated_violin_r <- srv_teal_data(id = "violin_decorator", data = undecorated_boxplot_r, data_module = decorators$violin, modules = module())
1131+
boxplot_r <- reactive({
1132+
req(input$boxplot_alts)
1133+
if (input$boxplot_alts == "Box plot") {
1134+
decorated_boxplot_r()
1135+
} else {
1136+
decorated_violin_r()
1137+
}
1138+
})
1139+
undecorated_density_plot_r <- reactive({
10321140
teal::validate_inputs(iv_r())
10331141
density_plot_q()[["g"]]
10341142
})
1035-
cumulative_plot_r <- reactive({
1143+
density_plot_r <- srv_teal_data(id = "density_decorator", data = undecorated_density_plot_r, data_module = decorators$density, modules = module())
1144+
undecorated_cumulative_plot_r <- reactive({
10361145
teal::validate_inputs(iv_r())
10371146
cumulative_plot_q()[["g"]]
10381147
})
1148+
cumulative_plot_r <- srv_teal_data(id = "cum_dist_decorator", data = undecorated_cumulative_plot_r, data_module = decorators$cum_dist, modules = module())
10391149

10401150
box_pws <- teal.widgets::plot_with_settings_srv(
10411151
id = "box_plot",

man/tm_a_regression.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/tm_outliers.Rd

Lines changed: 85 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)