Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update vignettes: "creating custom modules" #975

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@

## 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 @@

### 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 @@

```

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 @@
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 @@
}

# 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 @@
}

# 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 @@
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 @@

## `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.
Binary file modified vignettes/images/filter_panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading