From 08d3dcf66a965f6a7b36aed372966dfdd742eb6f Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Wed, 29 Jan 2025 21:35:19 -0800 Subject: [PATCH 01/14] tidy things up, add space for readibility --- vignettes/decorate-module-output.Rmd | 78 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/vignettes/decorate-module-output.Rmd b/vignettes/decorate-module-output.Rmd index c713231a5..1de857451 100644 --- a/vignettes/decorate-module-output.Rmd +++ b/vignettes/decorate-module-output.Rmd @@ -12,9 +12,10 @@ vignette: > ## Introduction -`teal` is a powerful `shiny`-based framework with built-in modules for interactive data analysis. -This document outlines the customization options available for modifying the output of `teal` modules. -You will learn how to use `teal_transform_module()` to modify and enhance the objects created by `teal::modules()`, +`teal` is a powerful `shiny`-based framework with built-in modules for interactive data analysis. +This document outlines the customization options available for modifying the output of `teal` modules. + +You will learn how to use `teal_transform_module()` to modify and enhance the objects created by `teal::modules()`, enabling you to tailor the outputs to your specific requirements without rewriting the original module code. ## Decorators @@ -43,22 +44,24 @@ It is recommended to review the module documentation or source code to understan ## Decorators in `teal` -One of ways of adjusting input data or customizing module outputs in `teal` is the usage of `transformators` +One of ways of adjusting input data or customizing module outputs in `teal` is the usage of `transformators` created through `teal_transform_module`. -In below chapter we will present how to create the simplest static decorator with just a server part. Later, we will -present examples on more advanced usage, where decorators contain UI. You will also learn about a convenience -function that makes it easier to write decorators, called `make_teal_transform_server()`. The chapter ends with an -example module that utilizes decorators and a snippet that uses this module in `teal` application. +In the chapter below, we will demonstrate how to create the simplest static decorator with only a server component. +Later, we will explore more advanced use cases where decorators include a UI. +You will also learn about a convenient function, `make_teal_transform_server()`, which simplifies writing decorators. + +The chapter concludes with an example module that utilizes decorators and a snippet demonstrating how to use this module in a `teal` application. ### Server The simplest way to create a decorator is to use `teal_transform_module()` with only `server` argument provided (i.e. without UI part). -This approach adds functionality solely to the server code of the module. -In the following example, we assume that the module contains an object (of class `ggplot2`) named `plot`. +This approach adds functionality solely to the server code of the module. + +In the following example, we assume that the module contains an object (of class `ggplot2`) named `plot`. We modify the title and x-axis label of plot: -```{r} +```{r, message = FALSE} library(teal) static_decorator <- teal_transform_module( label = "Static decorator", @@ -77,8 +80,8 @@ static_decorator <- teal_transform_module( ) ``` -To simplify the repetitive elements of writing new decorators -(e.g., `function(id, data), moduleServer, reactive, within(data, ...)`), +To simplify the repetitive elements of writing new decorators +(e.g., `function(id, data), moduleServer, reactive, within(data, ...)`), you can use the `make_teal_transform_server()` convenience function, which takes a `language` as input: ```{r} @@ -96,8 +99,8 @@ static_decorator_lang <- teal_transform_module( ### UI -To create a decorator with user interactivity, you can add (optional) UI part and use it in server accordingly (i.e. a typical `shiny` module). -In the example below, the x-axis title is set dynamically via a `textInput`, allowing users to specify their preferred label. +To create a decorator with user interactivity, you can add (optional) UI part and use it in server accordingly (i.e. a typical `shiny` module). +In the example below, the x-axis title is set dynamically via a `textInput`, allowing users to specify their preferred label. Note how the input parameters are passed to the `within()` function using its `...` argument. ```{r} @@ -127,7 +130,7 @@ interactive_decorator <- teal_transform_module( ) ``` -As in the earlier examples, `make_teal_transform_server()` can simplify the creation of the server component. +As in the earlier examples, `make_teal_transform_server()` can simplify the creation of the server component. This wrapper requires you to use `input` object names directly in the expression - note that we have `xlab(x_axis_table)` and not `my_title = input$x_axis_title` together with `xlab(my_title)`. ```{r} @@ -151,10 +154,11 @@ interactive_decorator_lang <- teal_transform_module( ## Handling Various Object Names -`teal_transform_module` relies on the names of objects created within a module. -Writing a decorator that applies to any module can be challenging because different modules may use different object names. +`teal_transform_module` relies on the names of objects created within a module. +Writing a decorator that applies to any module can be challenging since different modules may use different object names. It is recommended to create a library of decorator functions that can be adapted to the specific object names used in `teal` modules. -In the following example, focus on the `output_name` parameter to see how decorator can be applied to multiple modules: + +In the following example, pay attention to the `output_name` parameter to see how a decorator can be applied to multiple modules: ```{r} gg_xlab_decorator <- function(output_name) { @@ -210,12 +214,11 @@ failing_decorator <- teal_transform_module( ### Example Module -To include decorators in a `teal` module, pass them as arguments (`ui_args` and `server_args`) to the module’s `ui` and -`server` components, where they will be used by `ui/srv_teal_transform_module`. +To include decorators in a `teal` module, pass them as arguments (`ui_args` and `server_args`) to the module’s `ui` and +`server` components, where they will be used by `ui_transform_teal_data` and `srv_transform_teal_data`. Please find an example module for the sake of this article: - ```{r} tm_decorated_plot <- function(label = "module", transformators = list(), decorators = NULL) { checkmate::assert_list(decorators, "teal_transform_module", null.ok = TRUE) @@ -319,9 +322,9 @@ if (interactive()) { ### Example Module -It is possible to pass any number of decorators (n) to a module. -The example below demonstrates how to handle a dynamic number of decorators, allowing the user to choose which decorator to apply from a list. -This makes the module more flexible and capable of accommodating various customization requirements. +It is possible to pass any number of decorators (n) to a module. +The example below demonstrates how to handle a dynamic number of decorators, allowing the user to choose which decorator to apply from a list. +This approach enhances the module’s flexibility and makes it capable of accommodating various customization requirements. ```{r} library(ggplot2) @@ -390,6 +393,7 @@ tm_decorated_plot <- function(label = "module", decorators = NULL) { req(input$y, input$y %in% colnames(data()[[dataname()]])) input$y }) + plot_data <- reactive({ req(dataname(), x(), y()) within(data(), @@ -413,6 +417,7 @@ tm_decorated_plot <- function(label = "module", decorators = NULL) { data = plot_data, transformators = decorators[[selected_decorator()]] ) + decorated_data <- reactive(within(req(decorated_data_no_print()), expr = plot)) output$plot <- renderPlot(decorated_data()[["plot"]]) @@ -428,11 +433,11 @@ tm_decorated_plot <- function(label = "module", decorators = NULL) { } ``` -By order of the decorator we will: - +In the order of the decorators, we will: + 1. Change the x axis title -2. Change the y axis title -3. Replace the x axis title +2. Change the y axis title +3. Replace the x axis title ```{r} interactive_decorator_1 <- teal_transform_module( @@ -510,7 +515,7 @@ interactive_decorator_3 <- teal_transform_module( ### Application -As you might have noted, the x axis title from the first decorator will be used but won't show up on the resulting plot: +As you might have noticed, the x-axis title from the first decorator will be used but will not appear in the resulting plot: ```{r} app <- init( @@ -533,16 +538,16 @@ if (interactive()) { ``` -# Modules with Multiple Outputs +## Modules with Multiple Outputs In this section, we demonstrate how to extend a teal module to handle multiple outputs and allow separate decoration for each. Specifically, the module will have two outputs: - a `ggplot` plot -- and a table +- and a table We will apply independent decorators to each output. -## Example Module with Two Outputs +### Example Module with Two Outputs The following module generates both a scatter plot and a summary table. Each of these outputs can be decorated independently using decorators passed to the module: @@ -645,8 +650,7 @@ tm_decorated_plot_table <- function(label = "module with two outputs", decorator } ``` - -## Example Decorators +### Example Decorators 1. **Plot Decorator**: Adds a title to the plot. @@ -677,7 +681,6 @@ plot_decorator <- teal_transform_module( ) ``` - 2. **Table Decorator**: Adds row names to the summary table. ```{r} @@ -692,8 +695,7 @@ table_decorator <- teal_transform_module( ) ``` - -## Application +### Application ```{r} app <- init( From e97a7da97f3c577acb7b925b284637a75b20be22 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Thu, 30 Jan 2025 22:01:42 -0800 Subject: [PATCH 02/14] rewrite sections, add closing statement --- vignettes/decorate-module-output.Rmd | 426 ++------------------------- 1 file changed, 18 insertions(+), 408 deletions(-) diff --git a/vignettes/decorate-module-output.Rmd b/vignettes/decorate-module-output.Rmd index 1de857451..954999546 100644 --- a/vignettes/decorate-module-output.Rmd +++ b/vignettes/decorate-module-output.Rmd @@ -20,9 +20,13 @@ enabling you to tailor the outputs to your specific requirements without rewriti ## Decorators -In programming, **decoration** refers to the process of modifying an object while preserving its original class. For instance, given an object `x` of class `"my_class"`, a function `foo(x)` is considered a **decorator function** if it modifies `x` and returns an object that retains the same class. In this context, `x` is referred to as the **decorated object**, and `foo()` is the **decorator function** or **decorator**. Decorators can perform a variety of operations, such as adding new methods or modifying data, while ensuring the object remains compatible with its original usage. +In programming, **decoration** refers to the process of modifying an object while preserving its original class. +For instance, given an object `x` of class `"my_class"`, a function `foo(x)` is considered a **decorator function** if it modifies `x` and returns an object that retains the same class. +In this context, `x` is referred to as the **decorated object**, and `foo()` is the **decorator function** or **decorator**. Decorators can perform a variety of operations, such as adding new methods or modifying data, while ensuring the object remains compatible with its original usage. -In the context of `teal` applications, decoration is specifically used to modify module outputs, such as plots or tables. For example, consider a decorator function `add_title(x, )` that takes a `ggplot2` plot object (`x`) as input, applies a title modification, and returns a modified `ggplot2` plot object. This function qualifies as a decorator because it preserves the original class of the input object. Conversely, a function like `create_plot(, , )`, which generates a new plot object, is **not** a decorator, as it produces an output of a different class. +In the context of `teal` applications, decoration is specifically used to modify module outputs, such as plots or tables. +For example, consider a decorator function `add_title(x, )` that takes a `ggplot2` plot object (`x`) as input, applies a title modification, and returns a modified `ggplot2` plot object. +This function qualifies as a decorator because it preserves the original class of the input object. Conversely, a function like `create_plot(, , )`, which generates a new plot object, is **not** a decorator, as it produces an output of a different class. Preserving the object's class during decoration is essential for compatibility. It ensures that the subsequent "display" logic can seamlessly handle both decorated and non-decorated objects. @@ -44,8 +48,7 @@ It is recommended to review the module documentation or source code to understan ## Decorators in `teal` -One of ways of adjusting input data or customizing module outputs in `teal` is the usage of `transformators` -created through `teal_transform_module`. +One way to adjust input data or customize module outputs in `teal` is by using transformators created through `teal_transform_module()`. In the chapter below, we will demonstrate how to create the simplest static decorator with only a server component. Later, we will explore more advanced use cases where decorators include a UI. @@ -139,14 +142,14 @@ interactive_decorator_lang <- teal_transform_module( ui = function(id) { ns <- NS(id) div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis") + textInput(ns("y_axis_title"), "Y axis title", value = "y axis") ) }, server = make_teal_transform_server( expression( plot <- plot + ggtitle("This is title") + - xlab(x_axis_title) + ylab(y_axis_title) ) ) ) @@ -210,9 +213,7 @@ failing_decorator <- teal_transform_module( ) ``` -## Decorating Plots - -### Example Module +## Include Decorators in a `teal` Module To include decorators in a `teal` module, pass them as arguments (`ui_args` and `server_args`) to the module’s `ui` and `server` components, where they will be used by `ui_transform_teal_data` and `srv_transform_teal_data`. @@ -296,7 +297,10 @@ tm_decorated_plot <- function(label = "module", transformators = list(), decorat } ``` -### Application +## `teal` App With Decorators + +Now that we have the `teal` module ready, let's apply all the decorators we’ve created in our `teal` app. +Please note that a module can accept any number of decorators as demonstrated in the example below. ```{r} library(ggplot2) @@ -309,226 +313,8 @@ app <- init( tm_decorated_plot("interactive", decorators = list(interactive_decorator)), tm_decorated_plot("interactive-from lang", decorators = list(interactive_decorator_lang)), tm_decorated_plot("from-fun", decorators = list(gg_xlab_decorator("plot"))), - tm_decorated_plot("failing", decorators = list(failing_decorator)) - ) -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` - -## Multiple Decorators - -### Example Module - -It is possible to pass any number of decorators (n) to a module. -The example below demonstrates how to handle a dynamic number of decorators, allowing the user to choose which decorator to apply from a list. -This approach enhances the module’s flexibility and makes it capable of accommodating various customization requirements. - -```{r} -library(ggplot2) -tm_decorated_plot <- function(label = "module", decorators = NULL) { - checkmate::assert_list(decorators, "teal_transform_module", null.ok = TRUE) - module( - label = label, - ui = function(id, decorators) { - ns <- NS(id) - div( - selectInput(ns("dataname"), label = "Select dataset", choices = NULL), - selectInput(ns("x"), label = "Select x-axis", choices = NULL), - selectInput(ns("y"), label = "Select y-axis", choices = NULL), - selectInput( - ns("decorator_choice"), - "Choose decorator", - choices = names(decorators), - selected = names(decorators)[1] - ), - div( - id = ns("decorate_wrapper"), - lapply(names(decorators), function(decorator_name) { - div( - id = ns(paste0("decorate_", decorator_name)), - ui_transform_teal_data( - ns(paste0("decorate_", decorator_name)), - transformators = decorators[[decorator_name]] - ) - ) - }) - ), - plotOutput(ns("plot")), - verbatimTextOutput(ns("text")) - ) - }, - server = function(id, data, decorators) { - moduleServer(id, function(input, output, session) { - observeEvent(data(), { - updateSelectInput(inputId = "dataname", choices = names(data())) - }) - - dataname <- reactive(req(input$dataname)) - - observeEvent(dataname(), { - updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]])) - updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]])) - }) - - observeEvent(input$decorator_choice, { - # Dynamically show only the selected decorator's UI - lapply(names(decorators), function(decorator_name) { - if (decorator_name == input$decorator_choice) { - shinyjs::show(paste0("decorate_", decorator_name)) - } else { - shinyjs::hide(paste0("decorate_", decorator_name)) - } - }) - }) - - x <- reactive({ - req(input$x, input$x %in% colnames(data()[[dataname()]])) - input$x - }) - - y <- reactive({ - req(input$y, input$y %in% colnames(data()[[dataname()]])) - input$y - }) - - plot_data <- reactive({ - req(dataname(), x(), y()) - within(data(), - { - plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = x, y = y)) + - ggplot2::geom_point() - }, - dataname = as.name(dataname()), - x = as.name(x()), - y = as.name(y()) - ) - }) - - selected_decorator <- reactive({ - req(input$decorator_choice) - input$decorator_choice - }) - - decorated_data_no_print <- srv_transform_teal_data( - sprintf("decorate_%s", selected_decorator()), - data = plot_data, - transformators = decorators[[selected_decorator()]] - ) - - decorated_data <- reactive(within(req(decorated_data_no_print()), expr = plot)) - - output$plot <- renderPlot(decorated_data()[["plot"]]) - output$text <- renderText({ - req(input$decorator_choice) - teal.code::get_code(req(decorated_data())) - }) - }) - }, - ui_args = list(decorators = decorators), - server_args = list(decorators = decorators) - ) -} -``` - -In the order of the decorators, we will: - -1. Change the x axis title -2. Change the y axis title -3. Replace the x axis title - -```{r} -interactive_decorator_1 <- teal_transform_module( - label = "Interactive decorator 1", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis 1") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - xlab(title) - }, - title = input$x_axis_title - ) - }) - }) - } -) - -interactive_decorator_2 <- teal_transform_module( - label = "Interactive decorator 2", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("y_axis_title"), "Y axis title", value = "y axis 1") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - ylab(title) - }, - title = input$y_axis_title - ) - }) - }) - } -) - -interactive_decorator_3 <- teal_transform_module( - label = "Interactive decorator 3", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis 3") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - xlab(title) - }, - title = input$x_axis_title - ) - }) - }) - } -) -``` - -### Application - -As you might have noticed, the x-axis title from the first decorator will be used but will not appear in the resulting plot: - -```{r} -app <- init( - data = teal_data(iris = iris, mtcars = mtcars), - modules = modules( - tm_decorated_plot( - "dynamic_decorators", - decorators = list( - decorator_1 = interactive_decorator_1, - decorator_2 = interactive_decorator_2, - decorator_3 = interactive_decorator_3 - ) - ) + tm_decorated_plot("failing", decorators = list(failing_decorator)), + tm_decorated_plot("multiple decorators", decorators = list(interactive_decorator, interactive_decorator_lang)) ) ) @@ -537,181 +323,5 @@ if (interactive()) { } ``` - -## Modules with Multiple Outputs - -In this section, we demonstrate how to extend a teal module to handle multiple outputs and allow separate decoration for each. Specifically, the module will have two outputs: - -- a `ggplot` plot -- and a table - -We will apply independent decorators to each output. - -### Example Module with Two Outputs - -The following module generates both a scatter plot and a summary table. -Each of these outputs can be decorated independently using decorators passed to the module: - -```{r} -tm_decorated_plot_table <- function(label = "module with two outputs", decorators = list()) { - checkmate::assert_list(decorators, "teal_transform_module", null.ok = TRUE) - - module( - label = label, - ui = function(id, decorators) { - ns <- NS(id) - div( - selectInput(ns("dataname"), label = "Select dataset", choices = NULL), - selectInput(ns("x"), label = "Select x-axis", choices = NULL), - selectInput(ns("y"), label = "Select y-axis", choices = NULL), - ui_transform_teal_data(ns("decorate_plot"), transformators = decorators$plot), - ui_transform_teal_data(ns("decorate_table"), transformators = decorators$table), - plotOutput(ns("plot")), - tableOutput(ns("table")), - verbatimTextOutput(ns("text")) - ) - }, - server = function(id, data, decorators) { - moduleServer(id, function(input, output, session) { - observeEvent(data(), { - updateSelectInput(inputId = "dataname", choices = names(data())) - }) - - dataname <- reactive(req(input$dataname)) - - observeEvent(dataname(), { - updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]])) - updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]])) - }) - - x <- reactive({ - req(input$x, input$x %in% colnames(data()[[dataname()]])) - input$x - }) - - y <- reactive({ - req(input$y, input$y %in% colnames(data()[[dataname()]])) - input$y - }) - - # Generate plot data - plot_data <- reactive({ - req(dataname(), x(), y()) - within(data(), - { - plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = xvar, y = yvar)) + - ggplot2::geom_point() - }, - dataname = as.name(dataname()), - xvar = as.name(x()), - yvar = as.name(y()) - ) - }) - - # Generate table data - table_data <- reactive({ - req(dataname()) - within(data(), - { - table_data <- data.frame(Filter(Negate(is.na), lapply(dataname, mean, na.rm = TRUE))) - }, - dataname = as.name(dataname()) - ) - }) - - # Apply decorators to plot - decorated_plot <- srv_transform_teal_data( - "decorate_plot", - data = plot_data, - transformators = decorators$plot - ) - - # Apply decorators to table - decorated_table <- srv_transform_teal_data( - "decorate_table", - data = table_data, - transformators = decorators$table - ) - - output$plot <- renderPlot(decorated_plot()[["plot"]]) - - output$table <- renderTable(decorated_table()[["table_data"]]) - - output$text <- renderText({ - plot_code <- teal.code::get_code(req(decorated_plot())) - table_code <- teal.code::get_code(req(decorated_table())) - paste("# Plot Code:", plot_code, "\n\n# Table Code:", table_code) - }) - }) - }, - ui_args = list(decorators = decorators), - server_args = list(decorators = decorators) - ) -} -``` - -### Example Decorators - -1. **Plot Decorator**: Adds a title to the plot. - -```{r} -plot_decorator <- teal_transform_module( - label = "Decorate plot", - ui = function(id) { - ns <- NS(id) - textInput(ns("plot_title"), "Plot Title", value = "Decorated Title (editable)") - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + ggplot2::ggtitle(ptitle) + - ggplot2::theme_minimal() + - ggplot2::theme( - plot.title = element_text(face = "bold", size = 30, color = "blue") - ) - }, - ptitle = input$plot_title - ) - }) - }) - } -) -``` - -2. **Table Decorator**: Adds row names to the summary table. - -```{r} -table_decorator <- teal_transform_module( - label = "Decorate table", - ui = function(id) shiny::tags$p("No UI needed for table decorator and could be ommited."), - server = make_teal_transform_server( - expression({ - table_data[["Added by decorator"]] <- paste0("Row ", seq_len(nrow(table_data))) - }) - ) -) -``` - -### Application - -```{r} -app <- init( - data = teal_data(iris = iris, mtcars = mtcars), - modules = modules( - tm_decorated_plot_table( - "plot_and_table", - decorators = list( - plot = plot_decorator, - table = table_decorator - ) - ) - ) -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` +By utilizing `teal_transform_module()`, decorators can efficiently modify and enhance the outputs of a `teal` module without altering its original implementation. +Whether you need simple static adjustments or dynamic UI-driven transformations, decorators provide a powerful way to customize plots, tables, or any other module output. From f877b611515b7cde5cf037dc90c74b91cda0544f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ka=C5=82=C4=99dkowski?= Date: Fri, 31 Jan 2025 09:45:24 +0100 Subject: [PATCH 03/14] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @kumamiao suggestions Co-authored-by: Nina Qi Signed-off-by: Dawid Kałędkowski --- vignettes/decorate-module-output.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/decorate-module-output.Rmd b/vignettes/decorate-module-output.Rmd index 954999546..30774683d 100644 --- a/vignettes/decorate-module-output.Rmd +++ b/vignettes/decorate-module-output.Rmd @@ -13,7 +13,7 @@ vignette: > ## Introduction `teal` is a powerful `shiny`-based framework with built-in modules for interactive data analysis. -This document outlines the customization options available for modifying the output of `teal` modules. +Yet when these built-in module outputs don’t fully meet your needs, you have the flexibility to customize the output. This document outlines the customization options available for modifying the output of `teal` modules. You will learn how to use `teal_transform_module()` to modify and enhance the objects created by `teal::modules()`, enabling you to tailor the outputs to your specific requirements without rewriting the original module code. @@ -134,7 +134,7 @@ interactive_decorator <- teal_transform_module( ``` As in the earlier examples, `make_teal_transform_server()` can simplify the creation of the server component. -This wrapper requires you to use `input` object names directly in the expression - note that we have `xlab(x_axis_table)` and not `my_title = input$x_axis_title` together with `xlab(my_title)`. +This wrapper requires you to use `input` object names directly in the expression - note that we have `xlab(x_axis_title)` and not `my_title = input$x_axis_title` together with `xlab(my_title)`. ```{r} interactive_decorator_lang <- teal_transform_module( From d48ca51acfae894779ba9a9853246e772d420efb Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Fri, 31 Jan 2025 09:56:16 +0100 Subject: [PATCH 04/14] file name has to be the same as title --- _pkgdown.yml | 2 +- ...rate-module-output.Rmd => customizing-module-output.Rmd} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename vignettes/{decorate-module-output.Rmd => customizing-module-output.Rmd} (98%) diff --git a/_pkgdown.yml b/_pkgdown.yml index 1eea00f5f..c8c4b21e4 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -73,7 +73,7 @@ articles: contents: - creating-custom-modules - adding-support-for-reporting - - decorate-module-output + - customizing-module-output - title: 📃 Technical blueprint desc: > The purpose of the blueprint is to aid new developer’s comprehension of the diff --git a/vignettes/decorate-module-output.Rmd b/vignettes/customizing-module-output.Rmd similarity index 98% rename from vignettes/decorate-module-output.Rmd rename to vignettes/customizing-module-output.Rmd index 30774683d..3cd99b594 100644 --- a/vignettes/decorate-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -41,7 +41,7 @@ This powerful functionality empowers application developers to significantly cus To use decorators effectively, certain requirements must be met: -1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators. +1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators. See (Include Decorators in a `teal` Module)[#include-decorators-in-a-teal-Module]. 2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decoration. It is recommended to review the module documentation or source code to understand its internal object naming before applying decorators. @@ -56,7 +56,7 @@ You will also learn about a convenient function, `make_teal_transform_server()`, The chapter concludes with an example module that utilizes decorators and a snippet demonstrating how to use this module in a `teal` application. -### Server +### Non-interactive decorators The simplest way to create a decorator is to use `teal_transform_module()` with only `server` argument provided (i.e. without UI part). This approach adds functionality solely to the server code of the module. @@ -100,7 +100,7 @@ static_decorator_lang <- teal_transform_module( ) ``` -### UI +### Interactive decorators To create a decorator with user interactivity, you can add (optional) UI part and use it in server accordingly (i.e. a typical `shiny` module). In the example below, the x-axis title is set dynamically via a `textInput`, allowing users to specify their preferred label. From 242a64796c38e29d2c228985247793d07c2b4122 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Fri, 31 Jan 2025 10:06:45 +0100 Subject: [PATCH 05/14] fix link --- vignettes/customizing-module-output.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index 3cd99b594..9397d59b8 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -41,7 +41,7 @@ This powerful functionality empowers application developers to significantly cus To use decorators effectively, certain requirements must be met: -1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators. See (Include Decorators in a `teal` Module)[#include-decorators-in-a-teal-Module]. +1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators. See [Include Decorators in a `teal` Module](#include-decorators-in-a-teal-module). 2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decoration. It is recommended to review the module documentation or source code to understand its internal object naming before applying decorators. From 4484de70e58c3b8e70d0d4b0d41c96d748649b18 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Fri, 31 Jan 2025 09:41:20 -0800 Subject: [PATCH 06/14] Update vignettes/customizing-module-output.Rmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski Signed-off-by: Dony Unardi --- vignettes/customizing-module-output.Rmd | 1 - 1 file changed, 1 deletion(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index 9397d59b8..d90d6a93f 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -12,7 +12,6 @@ vignette: > ## Introduction -`teal` is a powerful `shiny`-based framework with built-in modules for interactive data analysis. Yet when these built-in module outputs don’t fully meet your needs, you have the flexibility to customize the output. This document outlines the customization options available for modifying the output of `teal` modules. You will learn how to use `teal_transform_module()` to modify and enhance the objects created by `teal::modules()`, From a70ff4d340a23667c0afe5e9cfa73760190bbe11 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Fri, 31 Jan 2025 13:28:57 -0800 Subject: [PATCH 07/14] delete old vignette --- vignettes/decorate-module-output.Rmd | 715 --------------------------- 1 file changed, 715 deletions(-) delete mode 100644 vignettes/decorate-module-output.Rmd diff --git a/vignettes/decorate-module-output.Rmd b/vignettes/decorate-module-output.Rmd deleted file mode 100644 index d8073dd43..000000000 --- a/vignettes/decorate-module-output.Rmd +++ /dev/null @@ -1,715 +0,0 @@ ---- -title: "Customizing Module Output" -author: "NEST CoreDev" -output: - rmarkdown::html_vignette: - toc: true -vignette: > - %\VignetteIndexEntry{Customizing Module Output} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -## Introduction - -`teal` is a powerful `shiny`-based framework with built-in modules for interactive data analysis. -This document outlines the customization options available for modifying the output of `teal` modules. -You will learn how to use `teal_transform_module()` to modify and enhance the objects created by `teal::modules()`, -enabling you to tailor the outputs to your specific requirements without rewriting the original module code. - -## Decorators - -In programming, **decoration** refers to the process of modifying an object while preserving its original class. For instance, given an object `x` of class `"my_class"`, a function `foo(x)` is considered a **decorator function** if it modifies `x` and returns an object that retains the same class. In this context, `x` is referred to as the **decorated object**, and `foo()` is the **decorator function** or **decorator**. Decorators can perform a variety of operations, such as adding new methods or modifying data, while ensuring the object remains compatible with its original usage. - -In the context of `teal` applications, decoration is specifically used to modify module outputs, such as plots or tables. For example, consider a decorator function `add_title(x, )` that takes a `ggplot2` plot object (`x`) as input, applies a title modification, and returns a modified `ggplot2` plot object. This function qualifies as a decorator because it preserves the original class of the input object. Conversely, a function like `create_plot(, , )`, which generates a new plot object, is **not** a decorator, as it produces an output of a different class. - -Preserving the object's class during decoration is essential for compatibility. It ensures that the subsequent "display" logic can seamlessly handle both decorated and non-decorated objects. - -The decoration process can vary in complexity: - -- **Simple Decorations**: Single-step modifications, such as a single method call that does not require additional data. -- **Complex Decorations**: Multi-step operations that may involve interdependent transformations, potentially requiring input from dedicated `shiny` UI elements. - -This powerful functionality empowers application developers to significantly customize outputs beyond the default capabilities provided by existing module parameters. Decorations allow for advanced modifications, enabling highly tailored and dynamic user experiences in `teal` applications. - -## Requirements and Limitations - -To use decorators effectively, certain requirements must be met: - -1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators. -2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decoration. - -It is recommended to review the module documentation or source code to understand its internal object naming before applying decorators. - -## Decorators in `teal` - -One of ways of adjusting input data or customizing module outputs in `teal` is the usage of `transformators` -created through `teal_transform_module`. - -In below chapter we will present how to create the simplest static decorator with just a server part. Later, we will -present examples on more advanced usage, where decorators contain UI. You will also learn about a convenience -function that makes it easier to write decorators, called `make_teal_transform_server()`. The chapter ends with an -example module that utilizes decorators and a snippet that uses this module in `teal` application. - -### Server - -The simplest way to create a decorator is to use `teal_transform_module()` with only `server` argument provided (i.e. without UI part). -This approach adds functionality solely to the server code of the module. -In the following example, we assume that the module contains an object (of class `ggplot2`) named `plot`. -We modify the title and x-axis label of plot: - -```{r message=FALSE, warning=FALSE} -library(teal) -static_decorator <- teal_transform_module( - label = "Static decorator", - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), { - plot <- plot + - ggtitle("This is title") + - xlab("x axis") - }) - }) - }) - } -) -``` - -To simplify the repetitive elements of writing new decorators -(e.g., `function(id, data), moduleServer, reactive, within(data, ...)`), -you can use the `make_teal_transform_server()` convenience function, which takes a `language` as input: - -```{r} -static_decorator_lang <- teal_transform_module( - label = "Static decorator (language)", - server = make_teal_transform_server( - expression( - plot <- plot + - ggtitle("This is title") + - xlab("x axis title") - ) - ) -) -``` - -### UI - -To create a decorator with user interactivity, you can add (optional) UI part and use it in server accordingly (i.e. a typical `shiny` module). -In the example below, the x-axis title is set dynamically via a `textInput`, allowing users to specify their preferred label. -Note how the input parameters are passed to the `within()` function using its `...` argument. - -```{r} -interactive_decorator <- teal_transform_module( - label = "Interactive decorator", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - ggtitle("This is title") + - xlab(my_title) - }, - my_title = input$x_axis_title - ) - }) - }) - } -) -``` - -As in the earlier examples, `make_teal_transform_server()` can simplify the creation of the server component. -This wrapper requires you to use `input` object names directly in the expression - note that we have `xlab(x_axis_table)` and not `my_title = input$x_axis_title` together with `xlab(my_title)`. - -```{r} -interactive_decorator_lang <- teal_transform_module( - label = "Interactive decorator (language)", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis") - ) - }, - server = make_teal_transform_server( - expression( - plot <- plot + - ggtitle("This is title") + - xlab(x_axis_title) - ) - ) -) -``` - -## Handling Various Object Names - -`teal_transform_module` relies on the names of objects created within a module. -Writing a decorator that applies to any module can be challenging because different modules may use different object names. -It is recommended to create a library of decorator functions that can be adapted to the specific object names used in `teal` modules. -In the following example, focus on the `output_name` parameter to see how decorator can be applied to multiple modules: - -```{r} -gg_xlab_decorator <- function(output_name) { - teal_transform_module( - label = "X-axis decorator", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - output_name <- output_name + - xlab(x_axis_title) - }, - x_axis_title = input$x_axis_title, - output_name = as.name(output_name) - ) - }) - }) - } - ) -} -``` - -Decorator failures are managed by an internal `teal` mechanism called **trigger on success**, which ensures that the `data` -object within the module remains intact. -If a decorator fails, the outputs will not be shown, and an appropriate error message will be displayed. - -```{r} -failing_decorator <- teal_transform_module( - label = "Failing decorator", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive(stop("\nThis is an error produced by decorator\n")) - }) - } -) -``` - -## Decorating Plots - -### Example Module - -To include decorators in a `teal` module, pass them as arguments (`ui_args` and `server_args`) to the module’s `ui` and -`server` components, where they will be used by `ui/srv_teal_transform_module`. - -Please find an example module for the sake of this article: - - -```{r} -tm_decorated_plot <- function(label = "module", transformators = list(), decorators = NULL) { - checkmate::assert_list(decorators, "teal_transform_module", null.ok = TRUE) - - module( - label = label, - ui = function(id, decorators) { - ns <- NS(id) - div( - selectInput(ns("dataname"), label = "select dataname", choices = NULL), - selectInput(ns("x"), label = "select x", choices = NULL), - selectInput(ns("y"), label = "select y", choices = NULL), - ui_transform_teal_data(ns("decorate"), transformators = decorators), - plotOutput(ns("plot")), - verbatimTextOutput(ns("text")) - ) - }, - server = function(id, data, decorators) { - moduleServer(id, function(input, output, session) { - observeEvent(data(), { - updateSelectInput(inputId = "dataname", choices = names(data())) - }) - - observeEvent(input$dataname, { - req(input$dataname) - updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]])) - updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]])) - }) - - dataname <- reactive(req(input$dataname)) - x <- reactive({ - req(input$x, input$x %in% colnames(data()[[dataname()]])) - input$x - }) - - y <- reactive({ - req(input$y, input$y %in% colnames(data()[[dataname()]])) - input$y - }) - plot_data <- reactive({ - req(dataname(), x(), y()) - within(data(), - { - plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = x, y = y)) + - ggplot2::geom_point() - }, - dataname = as.name(dataname()), - x = as.name(x()), - y = as.name(y()) - ) - }) - - plot_data_decorated_no_print <- srv_transform_teal_data( - "decorate", - data = plot_data, - transformators = decorators - ) - plot_data_decorated <- reactive( - within(req(plot_data_decorated_no_print()), expr = plot) - ) - - plot_r <- reactive({ - plot_data_decorated()[["plot"]] - }) - - output$plot <- renderPlot(plot_r()) - output$text <- renderText({ - teal.code::get_code(req(plot_data_decorated())) - }) - }) - }, - ui_args = list(decorators = decorators), - server_args = list(decorators = decorators) - ) -} -``` - -### Application - -```{r} -library(ggplot2) -app <- init( - data = teal_data(iris = iris, mtcars = mtcars), - modules = modules( - tm_decorated_plot("identity"), - tm_decorated_plot("no-ui", decorators = list(static_decorator)), - tm_decorated_plot("lang", decorators = list(static_decorator_lang)), - tm_decorated_plot("interactive", decorators = list(interactive_decorator)), - tm_decorated_plot("interactive-from lang", decorators = list(interactive_decorator_lang)), - tm_decorated_plot("from-fun", decorators = list(gg_xlab_decorator("plot"))), - tm_decorated_plot("failing", decorators = list(failing_decorator)) - ) -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` - -## Multiple Decorators - -### Example Module - -It is possible to pass any number of decorators (n) to a module. -The example below demonstrates how to handle a dynamic number of decorators, allowing the user to choose which decorator to apply from a list. -This makes the module more flexible and capable of accommodating various customization requirements. - -```{r} -library(ggplot2) -tm_decorated_plot <- function(label = "module", decorators = NULL) { - checkmate::assert_list(decorators, "teal_transform_module", null.ok = TRUE) - module( - label = label, - ui = function(id, decorators) { - ns <- NS(id) - div( - selectInput(ns("dataname"), label = "Select dataset", choices = NULL), - selectInput(ns("x"), label = "Select x-axis", choices = NULL), - selectInput(ns("y"), label = "Select y-axis", choices = NULL), - selectInput( - ns("decorator_choice"), - "Choose decorator", - choices = names(decorators), - selected = names(decorators)[1] - ), - div( - id = ns("decorate_wrapper"), - lapply(names(decorators), function(decorator_name) { - div( - id = ns(paste0("decorate_", decorator_name)), - ui_transform_teal_data( - ns(paste0("decorate_", decorator_name)), - transformators = decorators[[decorator_name]] - ) - ) - }) - ), - plotOutput(ns("plot")), - verbatimTextOutput(ns("text")) - ) - }, - server = function(id, data, decorators) { - moduleServer(id, function(input, output, session) { - observeEvent(data(), { - updateSelectInput(inputId = "dataname", choices = names(data())) - }) - - dataname <- reactive(req(input$dataname)) - - observeEvent(dataname(), { - updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]])) - updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]])) - }) - - observeEvent(input$decorator_choice, { - # Dynamically show only the selected decorator's UI - lapply(names(decorators), function(decorator_name) { - if (decorator_name == input$decorator_choice) { - shinyjs::show(paste0("decorate_", decorator_name)) - } else { - shinyjs::hide(paste0("decorate_", decorator_name)) - } - }) - }) - - x <- reactive({ - req(input$x, input$x %in% colnames(data()[[dataname()]])) - input$x - }) - - y <- reactive({ - req(input$y, input$y %in% colnames(data()[[dataname()]])) - input$y - }) - plot_data <- reactive({ - req(dataname(), x(), y()) - within(data(), - { - plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = x, y = y)) + - ggplot2::geom_point() - }, - dataname = as.name(dataname()), - x = as.name(x()), - y = as.name(y()) - ) - }) - - selected_decorator <- reactive({ - req(input$decorator_choice) - input$decorator_choice - }) - - decorated_data_no_print <- srv_transform_teal_data( - sprintf("decorate_%s", selected_decorator()), - data = plot_data, - transformators = decorators[[selected_decorator()]] - ) - decorated_data <- reactive(within(req(decorated_data_no_print()), expr = plot)) - - output$plot <- renderPlot(decorated_data()[["plot"]]) - output$text <- renderText({ - req(input$decorator_choice) - teal.code::get_code(req(decorated_data())) - }) - }) - }, - ui_args = list(decorators = decorators), - server_args = list(decorators = decorators) - ) -} -``` - -By order of the decorator we will: - -1. Change the x axis title -2. Change the y axis title -3. Replace the x axis title - -```{r} -interactive_decorator_1 <- teal_transform_module( - label = "Interactive decorator 1", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis 1") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - xlab(title) - }, - title = input$x_axis_title - ) - }) - }) - } -) - -interactive_decorator_2 <- teal_transform_module( - label = "Interactive decorator 2", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("y_axis_title"), "Y axis title", value = "y axis 1") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - ylab(title) - }, - title = input$y_axis_title - ) - }) - }) - } -) - -interactive_decorator_3 <- teal_transform_module( - label = "Interactive decorator 3", - ui = function(id) { - ns <- NS(id) - div( - textInput(ns("x_axis_title"), "X axis title", value = "x axis 3") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + - xlab(title) - }, - title = input$x_axis_title - ) - }) - }) - } -) -``` - -### Application - -As you might have noted, the x axis title from the first decorator will be used but won't show up on the resulting plot: - -```{r} -app <- init( - data = teal_data(iris = iris, mtcars = mtcars), - modules = modules( - tm_decorated_plot( - "dynamic_decorators", - decorators = list( - decorator_1 = interactive_decorator_1, - decorator_2 = interactive_decorator_2, - decorator_3 = interactive_decorator_3 - ) - ) - ) -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` - - -# Modules with Multiple Outputs - -In this section, we demonstrate how to extend a teal module to handle multiple outputs and allow separate decoration for each. Specifically, the module will have two outputs: - -- a `ggplot` plot -- and a table - -We will apply independent decorators to each output. - -## Example Module with Two Outputs - -The following module generates both a scatter plot and a summary table. -Each of these outputs can be decorated independently using decorators passed to the module: - -```{r} -tm_decorated_plot_table <- function(label = "module with two outputs", decorators = list()) { - checkmate::assert_list(decorators, "teal_transform_module", null.ok = TRUE) - - module( - label = label, - ui = function(id, decorators) { - ns <- NS(id) - div( - selectInput(ns("dataname"), label = "Select dataset", choices = NULL), - selectInput(ns("x"), label = "Select x-axis", choices = NULL), - selectInput(ns("y"), label = "Select y-axis", choices = NULL), - ui_transform_teal_data(ns("decorate_plot"), transformators = decorators$plot), - ui_transform_teal_data(ns("decorate_table"), transformators = decorators$table), - plotOutput(ns("plot")), - tableOutput(ns("table")), - verbatimTextOutput(ns("text")) - ) - }, - server = function(id, data, decorators) { - moduleServer(id, function(input, output, session) { - observeEvent(data(), { - updateSelectInput(inputId = "dataname", choices = names(data())) - }) - - dataname <- reactive(req(input$dataname)) - - observeEvent(dataname(), { - updateSelectInput(inputId = "x", choices = colnames(data()[[input$dataname]])) - updateSelectInput(inputId = "y", choices = colnames(data()[[input$dataname]])) - }) - - x <- reactive({ - req(input$x, input$x %in% colnames(data()[[dataname()]])) - input$x - }) - - y <- reactive({ - req(input$y, input$y %in% colnames(data()[[dataname()]])) - input$y - }) - - # Generate plot data - plot_data <- reactive({ - req(dataname(), x(), y()) - within(data(), - { - plot <- ggplot2::ggplot(dataname, ggplot2::aes(x = xvar, y = yvar)) + - ggplot2::geom_point() - }, - dataname = as.name(dataname()), - xvar = as.name(x()), - yvar = as.name(y()) - ) - }) - - # Generate table data - table_data <- reactive({ - req(dataname()) - within(data(), - { - table_data <- data.frame(Filter(Negate(is.na), lapply(dataname, mean, na.rm = TRUE))) - }, - dataname = as.name(dataname()) - ) - }) - - # Apply decorators to plot - decorated_plot <- srv_transform_teal_data( - "decorate_plot", - data = plot_data, - transformators = decorators$plot - ) - - # Apply decorators to table - decorated_table <- srv_transform_teal_data( - "decorate_table", - data = table_data, - transformators = decorators$table - ) - - output$plot <- renderPlot(decorated_plot()[["plot"]]) - - output$table <- renderTable(decorated_table()[["table_data"]]) - - output$text <- renderText({ - plot_code <- teal.code::get_code(req(decorated_plot())) - table_code <- teal.code::get_code(req(decorated_table())) - paste("# Plot Code:", plot_code, "\n\n# Table Code:", table_code) - }) - }) - }, - ui_args = list(decorators = decorators), - server_args = list(decorators = decorators) - ) -} -``` - - -## Example Decorators - -1. **Plot Decorator**: Adds a title to the plot. - -```{r} -plot_decorator <- teal_transform_module( - label = "Decorate plot", - ui = function(id) { - ns <- NS(id) - textInput(ns("plot_title"), "Plot Title", value = "Decorated Title (editable)") - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - req(data()) - within(data(), - { - plot <- plot + ggplot2::ggtitle(ptitle) + - ggplot2::theme_minimal() + - ggplot2::theme( - plot.title = element_text(face = "bold", size = 30, color = "blue") - ) - }, - ptitle = input$plot_title - ) - }) - }) - } -) -``` - - -2. **Table Decorator**: Adds row names to the summary table. - -```{r} -table_decorator <- teal_transform_module( - label = "Decorate table", - ui = function(id) shiny::tags$p("No UI needed for table decorator and could be ommited."), - server = make_teal_transform_server( - expression({ - table_data[["Added by decorator"]] <- paste0("Row ", seq_len(nrow(table_data))) - }) - ) -) -``` - - -## Application - -```{r} -app <- init( - data = teal_data(iris = iris, mtcars = mtcars), - modules = modules( - tm_decorated_plot_table( - "plot_and_table", - decorators = list( - plot = plot_decorator, - table = table_decorator - ) - ) - ) -) - -if (interactive()) { - shinyApp(app$ui, app$server) -} -``` From af32a06b5990e4f6cbd18680cd088697781ac141 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Fri, 31 Jan 2025 14:42:13 -0800 Subject: [PATCH 08/14] apply suggestions, update intro statement --- vignettes/customizing-module-output.Rmd | 31 ++++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index d90d6a93f..75fb7df90 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -12,36 +12,39 @@ vignette: > ## Introduction -Yet when these built-in module outputs don’t fully meet your needs, you have the flexibility to customize the output. This document outlines the customization options available for modifying the output of `teal` modules. +`teal` provides a collection of ready-to-use modules, each with a predefined way of presenting outputs. + +While these built-in modules cover many common use cases, you may need to customize their outputs to better fit your specific requirements. +This document outlines the available customization options for modifying `teal` module outputs. You will learn how to use `teal_transform_module()` to modify and enhance the objects created by `teal::modules()`, -enabling you to tailor the outputs to your specific requirements without rewriting the original module code. +allowing you to tailor the outputs without rewriting the original module code. ## Decorators -In programming, **decoration** refers to the process of modifying an object while preserving its original class. -For instance, given an object `x` of class `"my_class"`, a function `foo(x)` is considered a **decorator function** if it modifies `x` and returns an object that retains the same class. -In this context, `x` is referred to as the **decorated object**, and `foo()` is the **decorator function** or **decorator**. Decorators can perform a variety of operations, such as adding new methods or modifying data, while ensuring the object remains compatible with its original usage. +In the context of `teal` applications, decorators are specifically used to modify module outputs, such as plots or tables. -In the context of `teal` applications, decoration is specifically used to modify module outputs, such as plots or tables. For example, consider a decorator function `add_title(x, )` that takes a `ggplot2` plot object (`x`) as input, applies a title modification, and returns a modified `ggplot2` plot object. -This function qualifies as a decorator because it preserves the original class of the input object. Conversely, a function like `create_plot(, , )`, which generates a new plot object, is **not** a decorator, as it produces an output of a different class. +This function qualifies as a decorator because it preserves the original class of the input object. +Conversely, a function like `create_plot(, , )`, which generates a new plot object, is **not** a decorator, as it produces an output of a different class. -Preserving the object's class during decoration is essential for compatibility. It ensures that the subsequent "display" logic can seamlessly handle both decorated and non-decorated objects. +Preserving the object's class during decorator process is essential for compatibility. +It ensures that the subsequent "display" logic can seamlessly handle both decorated and non-decorated objects. -The decoration process can vary in complexity: +The decorators process can vary in complexity: -- **Simple Decorations**: Single-step modifications, such as a single method call that does not require additional data. -- **Complex Decorations**: Multi-step operations that may involve interdependent transformations, potentially requiring input from dedicated `shiny` UI elements. +- **Simple Decorators**: Single-step modifications, such as a single method call that does not require additional data. +- **Complex Decorators**: Multi-step operations that may involve interdependent transformations, potentially requiring input from dedicated `shiny` UI elements. -This powerful functionality empowers application developers to significantly customize outputs beyond the default capabilities provided by existing module parameters. Decorations allow for advanced modifications, enabling highly tailored and dynamic user experiences in `teal` applications. +This functionality empowers application developers to significantly customize outputs beyond the default capabilities provided by existing module parameters. +Decorators allow for advanced modifications, enabling highly tailored and dynamic user experiences in `teal` applications. ## Requirements and Limitations To use decorators effectively, certain requirements must be met: -1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators. See [Include Decorators in a `teal` Module](#include-decorators-in-a-teal-module). -2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decoration. +1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators (see [Include Decorators in a `teal` Module](#include-decorators-in-a-teal-module)). +2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decorator. It is recommended to review the module documentation or source code to understand its internal object naming before applying decorators. From b09767b5476a7ee5c64b9384b97b288aca4679d9 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Fri, 31 Jan 2025 14:52:57 -0800 Subject: [PATCH 09/14] changes in intro and decorator section --- vignettes/customizing-module-output.Rmd | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index 75fb7df90..e9503e4ff 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -22,14 +22,11 @@ allowing you to tailor the outputs without rewriting the original module code. ## Decorators -In the context of `teal` applications, decorators are specifically used to modify module outputs, such as plots or tables. +In `teal`, decorators are used to modify module outputs, such as plots or tables, without changing their core structure. -For example, consider a decorator function `add_title(x, )` that takes a `ggplot2` plot object (`x`) as input, applies a title modification, and returns a modified `ggplot2` plot object. -This function qualifies as a decorator because it preserves the original class of the input object. -Conversely, a function like `create_plot(, , )`, which generates a new plot object, is **not** a decorator, as it produces an output of a different class. - -Preserving the object's class during decorator process is essential for compatibility. -It ensures that the subsequent "display" logic can seamlessly handle both decorated and non-decorated objects. +For example, imagine you have a plot and you want to add a title. +A decorator function, like `add_title(x, "My Plot")`, takes an existing plot and updates it by adding a title while keeping everything else the same. +This makes it different from a function that creates a brand-new plot from scratch, as decorators work by enhancing what’s already there. The decorators process can vary in complexity: From cdbeaafeafc563ec095580a82bb47c94fee29890 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Mon, 3 Feb 2025 14:51:15 -0800 Subject: [PATCH 10/14] Update vignettes/customizing-module-output.Rmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski Signed-off-by: Dony Unardi --- vignettes/customizing-module-output.Rmd | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index e9503e4ff..5b03324c4 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -24,9 +24,7 @@ allowing you to tailor the outputs without rewriting the original module code. In `teal`, decorators are used to modify module outputs, such as plots or tables, without changing their core structure. -For example, imagine you have a plot and you want to add a title. -A decorator function, like `add_title(x, "My Plot")`, takes an existing plot and updates it by adding a title while keeping everything else the same. -This makes it different from a function that creates a brand-new plot from scratch, as decorators work by enhancing what’s already there. +For example, imagine you have a `plot` object (ggplot) and you want to add a title. To do so one simply needs to call `plot <- plot + ggtitle("My Plot")`. It is important to enhance the current object object instead of creating a new one. We call this operation a decoration. The decorators process can vary in complexity: From 23a3de9cd8783df35030468d72e9079c47b7a5c3 Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Mon, 3 Feb 2025 14:51:31 -0800 Subject: [PATCH 11/14] Update vignettes/customizing-module-output.Rmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski Signed-off-by: Dony Unardi --- vignettes/customizing-module-output.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index 5b03324c4..4166661d7 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -41,7 +41,7 @@ To use decorators effectively, certain requirements must be met: 1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators (see [Include Decorators in a `teal` Module](#include-decorators-in-a-teal-module)). 2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decorator. -It is recommended to review the module documentation or source code to understand its internal object naming before applying decorators. +It is recommended to review the module documentation or source code to understand its internal object naming and object class before applying decorators. ## Decorators in `teal` From 47f096e5fa7e604792a5df01de76ddc91949cf0d Mon Sep 17 00:00:00 2001 From: Dony Unardi Date: Mon, 3 Feb 2025 14:52:29 -0800 Subject: [PATCH 12/14] Update vignettes/customizing-module-output.Rmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski Signed-off-by: Dony Unardi --- vignettes/customizing-module-output.Rmd | 1 + 1 file changed, 1 insertion(+) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index 4166661d7..cba518c89 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -40,6 +40,7 @@ To use decorators effectively, certain requirements must be met: 1. **Module Support**: While `teal` provides the core functionality for decorators, the module must explicitly support this functionality. Developers should ensure that the module has been designed to work with decorators (see [Include Decorators in a `teal` Module](#include-decorators-in-a-teal-module)). 2. **Matching Object Names**: Decorators must reference object names that align with the internal naming conventions of the module. Each module may use different names for its output objects, such as `plot` or `table`. This alignment is critical for successful decorator. +3. **Matching Object Class**: Decorated objects are used in certain context and they are use to be consumed by relevant `render*` functions. For example `ggplot` objects are used in `renderPlot` while `plotly` objects are consumed by `renderPlotly`. It is important that the object doesn't change its basic class so the module can deal with the modified object. It is recommended to review the module documentation or source code to understand its internal object naming and object class before applying decorators. From cf9fdf4b750dade6b9ecbd60c268b53b06de33fd Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 4 Feb 2025 08:45:32 +0100 Subject: [PATCH 13/14] typo and remove unnecessary --- vignettes/customizing-module-output.Rmd | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vignettes/customizing-module-output.Rmd b/vignettes/customizing-module-output.Rmd index cba518c89..e3772b526 100644 --- a/vignettes/customizing-module-output.Rmd +++ b/vignettes/customizing-module-output.Rmd @@ -24,16 +24,13 @@ allowing you to tailor the outputs without rewriting the original module code. In `teal`, decorators are used to modify module outputs, such as plots or tables, without changing their core structure. -For example, imagine you have a `plot` object (ggplot) and you want to add a title. To do so one simply needs to call `plot <- plot + ggtitle("My Plot")`. It is important to enhance the current object object instead of creating a new one. We call this operation a decoration. +For example, imagine you have a `plot` object (`ggplot`) and you want to add a title. To do so one simply needs to call `plot <- plot + ggtitle("My Plot")`. It is important to enhance the current object object instead of creating a new one. We call this operation a decoration. The decorators process can vary in complexity: - **Simple Decorators**: Single-step modifications, such as a single method call that does not require additional data. - **Complex Decorators**: Multi-step operations that may involve interdependent transformations, potentially requiring input from dedicated `shiny` UI elements. -This functionality empowers application developers to significantly customize outputs beyond the default capabilities provided by existing module parameters. -Decorators allow for advanced modifications, enabling highly tailored and dynamic user experiences in `teal` applications. - ## Requirements and Limitations To use decorators effectively, certain requirements must be met: @@ -323,3 +320,4 @@ if (interactive()) { By utilizing `teal_transform_module()`, decorators can efficiently modify and enhance the outputs of a `teal` module without altering its original implementation. Whether you need simple static adjustments or dynamic UI-driven transformations, decorators provide a powerful way to customize plots, tables, or any other module output. + From e6762dcc2e99e88d06d359e2b84a2e313d3032d0 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 4 Feb 2025 09:45:43 +0100 Subject: [PATCH 14/14] restart cicd