Skip to content

Commit

Permalink
rewrite example and minor corrections
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksander Chlebowski committed Nov 27, 2023
1 parent c2120d4 commit 5c85c9a
Showing 1 changed file with 41 additions and 55 deletions.
96 changes: 41 additions & 55 deletions vignettes/creating-custom-modules.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ vignette: >

## Introduction

The `teal` framework provides a large number of analysis modules to be incorporated into `teal` applications. However, it is also possible to create your own modules using the `module` function.
The `teal` framework provides a large number of analysis modules to be incorporated into `teal` applications.
However, it is also possible to create your own modules using the `module` function.

Here is an implementation of a simple module:

Expand Down Expand Up @@ -44,10 +45,11 @@ which can be added into `teal` apps using `example_module(label = "Label for tab

### UI function

This function contains the UI required for the module. It should be a function with at least the arguments `id`. It can also contain the argument `data` for access to the application data. See the server section below for more details.
This function contains the UI required for the module. It should be a function with at least the arguments `id`.
It can also contain the argument `data` for access to the application data. See the server section below for more details.

The UI function can contain standard UI components alongside additional widgets provided by the `teal.widgets` package. In the example above we are using the `standard_layout` function of `teal.widgets` which generates a layout
including an encoding panel on the left and main output covering the rest of the module's UI.
The UI function can contain standard UI components alongside additional widgets provided by the `teal.widgets` package.
In the example above we are using the `standard_layout` function of `teal.widgets` which generates a layout including an encoding panel on the left and main output covering the rest of the module's UI.

### Server function

Expand All @@ -66,15 +68,13 @@ function(id,
```

When used inside a `teal` application called with `init`, the `data` argument is a named list of reactive `data.frame`s containing the data after having been filtered through the filter panel. It is of the `tdata` type and can be created using the `new_tdata` function.
When used inside a `teal` application called with `init`, the `data` argument receives a `teal_data` object containing the data (`data.frame`s) after having been filtered through the filter panel.

## A More Complicated Example

The `teal` framework also provides:

- A way to create modules which then generate the R code needed to reproduce their outputs; these modules use the [`teal.code`](https://insightsengineering.github.io/teal.code/) package.
- A way extract from and merge related datasets using the [`teal.transform`](https://insightsengineering.github.io/teal.transform/) package.
- A way to allow app creators to customize your modules also using `teal.transform`.

The annotated example below demonstrates these features within a simple histogram module, allowing app developers to choose the data and columns that their app users can select for display in a histogram.

Expand All @@ -84,18 +84,14 @@ See the package and function documentation for further details.
library(teal)
# ui function for the module
# histogram_var is a teal.transform::data_extract_spec object
# specifying which columns of which datasets users can choose
# allows for selecting dataset and a numeric variable from it
ui_histogram_example <- function(id, histogram_var) {
ns <- NS(id)
teal.widgets::standard_layout(
output = plotOutput(ns("plot")),
encoding = div(
teal.transform::data_extract_ui(
id = ns("histogram_var"),
label = "Variable",
data_extract_spec = histogram_var
)
uiOutput(ns("datasets")),
uiOutput(ns("numerics"))
),
# we have a show R code button to show the code needed
# to generate the histogram
Expand All @@ -104,37 +100,38 @@ ui_histogram_example <- function(id, histogram_var) {
}
# server function for the module
# histogram_var is a teal.transform::data_extract_spec object
# specifying which columns of which datasets users can choose
# presents choices for selection of dataset and then numeric variable
# plots histogram of selected variable
# records code
srv_histogram_example <- function(id, data, histogram_var) {
checkmate::assert_class(data, "tdata")
checkmate::assert_class(data, "reactive")
checkmate::assert_class(isolate(data()), "teal_data")
moduleServer(id, function(input, output, session) {
# get the selected dataset and column from the UI
extracted <- teal.transform::data_extract_srv(
id = "histogram_var",
datasets = data,
data_extract_spec = histogram_var,
join_keys = teal.data::join_keys(data)
)
ns <- session$ns
dataname <- reactive(extracted()$dataname)
selected <- reactive(extracted()$select)
# present data sets columns for selection
output$datasets <- renderUI({
selectInput(ns("datasets"), "select dataset", choices = datanames(data()))
})
dataset <- reactive(input$datasets)
# present numeric columns for selection
output$numerics <- renderUI({
req(dataset())
dataset <- data()[[dataset()]]
nums <- vapply(dataset, is.numeric, logical(1L))
selectInput(ns("numerics"), "select numeric variable", choices = names(nums[nums]))
})
selected <- reactive(input$numerics)
# the reactive which adds the code to plot the histogram into the qenv
plot_code_q <- reactive({
validate(need(length(selected) == 1, "Please select a variable"))
validate(need(length(dataset()) == 1, "Please select a dataset"))
validate(need(length(selected()) == 1, "Please select a variable"))
req(selected() %in% names(data()[[dataset()]]))
# take the filtered data from the data object and add it into the qenv environment
teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>%
teal.code::eval_code(
substitute(
expr = p <- hist(dataname[, selected]),
env = list(
dataname = as.name(dataname()),
selected = selected()
)
)
)
within(data(), p <- hist(dataset[, selected], las = 1), dataset = as.name(dataset()), selected = selected())
})
# shiny component to view
Expand All @@ -152,16 +149,13 @@ srv_histogram_example <- function(id, data, histogram_var) {
}
# the function which creates the teal module for users
tm_histogram_example <- function(label, histogram_var) {
tm_histogram_example <- function(label) {
checkmate::assert_character(label)
checkmate::assert_class(histogram_var, "data_extract_spec")
module(
label = label,
server = srv_histogram_example,
ui = ui_histogram_example,
ui_args = list(histogram_var = histogram_var),
server_args = list(histogram_var = histogram_var),
datanames = "all"
)
}
Expand All @@ -176,19 +170,8 @@ An example `teal` application using this module is shown below:
library(teal)
app <- init(
data = teal_data(
IRIS = iris,
code = "IRIS <- iris"
),
modules = tm_histogram_example(
label = "Simple Module",
histogram_var = data_extract_spec(
dataname = "IRIS",
select = select_spec(
choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
)
)
),
data = teal_data(IRIS = iris, NPK = npk, code = expression(IRIS <- iris, NPK <- npk)),

Check warning on line 173 in vignettes/creating-custom-modules.Rmd

View workflow job for this annotation

GitHub Actions / SuperLinter 🦸‍♀️ / Lint R code 🧶

file=vignettes/creating-custom-modules.Rmd,line=173,col=62,[object_name_linter] Variable and function name style should match snake_case or symbols.

Check warning on line 173 in vignettes/creating-custom-modules.Rmd

View workflow job for this annotation

GitHub Actions / SuperLinter 🦸‍♀️ / Lint R code 🧶

file=vignettes/creating-custom-modules.Rmd,line=173,col=76,[object_name_linter] Variable and function name style should match snake_case or symbols.
modules = tm_histogram_example(label = "Simple Module"),
header = "Simple app with custom histogram module"
)
Expand All @@ -199,7 +182,10 @@ if (interactive()) {

## `shiny` input cycle

When `teal` modules are run inside the `init` the initial shiny input cycle is empty for each of them. In practice, this means that some inputs might be initialized with `NULL` value, unnecessary triggering some observers. A developer has to be aware of this situation as often it will require `shiny::req` or `ignoreInit` argument in observers or `reactive` expressions. This side effect is caused by the `shiny::insertUI` function. We are aware of this inconvenience and have already started to look for a solution.
When `teal` modules are run inside the `init` the initial shiny input cycle is empty for each of them.
In practice, this means that some inputs might be initialized with `NULL` value, unnecessary triggering some observers.
A developer has to be aware of this situation as often it will require `shiny::req` or `ignoreInit` argument in observers or `reactive` expressions.
This side effect is caused by the `shiny::insertUI` function. We are aware of this inconvenience and have already started to look for a solution.

## Adding reporting to a module
Refer to `vignette("adding_support_for_reporting")` to read about adding support for reporting in your `teal` module.

0 comments on commit 5c85c9a

Please sign in to comment.