Creating Custom Modules
NEST CoreDev
Source:vignettes/creating-custom-modules.Rmd
creating-custom-modules.Rmd
Introduction
The teal
framework provides a large catalog of
plug-in-ready analysis modules to be incorporated into teal
applications. However, it is also possible to create your own modules
using the module
function.
Components of a module
UI function
This function contains the UI required for the module. It should be a
function with at least the argument id
. See the server
section below for more details.
Server function
This function contains the shiny
server logic for the
module and should be of the form:
function(
id,
data, # optional; use if module needs access to application data
filter_panel_api, # optional; use if module needs access to filter panel; see teal.slice
reporter, # optional; use if module supports reporting; see reporting vignette
...) {
moduleServer(id, function(input, output, session) {
# module code here
})
}
The data that arrives in the module is a teal_data
object, the data container used throughout the teal
application. teal_data
is passed to the init
function when building the application and, after filtering by the
filter panel, it is passed to modules, wrapped in a reactive expression.
The teal_data
class allows modules to track the
R
code that they execute so that module outputs can be
reproduced. See the teal.data
package for a detailed
explanation.
Example modules
Viewing data
Here is a minimal module that allows the user to select and view one
dataset at a time. By default, filtering is enabled for all datasets.
Note that dataset choices are specified by the datanames
property of the teal_data
container.
library(teal)
example_module <- function(label = "example teal module") {
checkmate::assert_string(label)
module(
label = label,
server = function(id, data) {
checkmate::assert_class(data, "reactive")
checkmate::assert_class(isolate(data()), "teal_data")
moduleServer(id, function(input, output, session) {
updateSelectInput(session, "dataname", choices = isolate(datanames(data())))
output$dataset <- renderPrint({
req(input$dataname)
data()[[input$dataname]]
})
})
},
ui = function(id) {
ns <- NS(id)
sidebarLayout(
sidebarPanel(selectInput(ns("dataname"), "Choose a dataset", choices = NULL)),
mainPanel(verbatimTextOutput(ns("dataset")))
)
}
)
}
Interacting with data and viewing code
The example below allows the user to interact with the data to create a simple visualization. In addition, it prints the code that can be used to reproduce that visualization.
library(teal)
# ui function for the module
# allows for selecting dataset and one of its numeric variables
ui_histogram_example <- function(id) {
ns <- NS(id)
sidebarLayout(
sidebarPanel(
selectInput(ns("datasets"), "select dataset", choices = NULL),
selectInput(ns("numerics"), "select numeric variable", choices = NULL)
),
mainPanel(
plotOutput(ns("plot")),
verbatimTextOutput(ns("code"))
),
)
}
# server function for the module
# presents datasets and numeric variables for selection
# displays a histogram of the selected variable
# displays code to reproduce the histogram
srv_histogram_example <- function(id, data) {
checkmate::assert_class(data, "reactive")
checkmate::assert_class(isolate(data()), "teal_data")
moduleServer(id, function(input, output, session) {
# update dataset and variable choices
# each selection stored in separate reactive expression
updateSelectInput(inputId = "datasets", choices = isolate(datanames(data())))
observe({
req(dataset())
nums <- vapply(data()[[dataset()]], is.numeric, logical(1L))
updateSelectInput(inputId = "numerics", choices = names(nums[nums]))
})
dataset <- reactive(input$datasets)
selected <- reactive(input$numerics)
# add plot code
plot_code_q <- reactive({
validate(need(length(dataset()) == 1L, "Please select a dataset"))
validate(need(length(selected()) == 1L, "Please select a variable"))
req(selected() %in% names(data()[[dataset()]]))
# evaluate plotting expression within data
# inject input values into plotting expression
within(
data(),
p <- hist(dataset[, selected], las = 1),
dataset = as.name(dataset()), selected = selected()
)
})
# view plot
output$plot <- renderPlot({
plot_code_q()[["p"]]
})
# view code
output$code <- renderPrint({
plot_code_q() %>%
get_code() %>%
cat()
})
})
}
# function that creates module instance to use in `teal` app
tm_histogram_example <- function(label) {
module(
label = label,
server = srv_histogram_example,
ui = ui_histogram_example,
datanames = "all"
)
}
This module is ready to be used in a teal
app.
app <- init(
data = teal_data(IRIS = iris, NPK = npk),
modules = tm_histogram_example(label = "Histogram Module"),
header = "Simple app with custom histogram module"
)
if (interactive()) {
shinyApp(app$ui, app$server)
}
Adding reporting to a module
Refer to this
vignette to read about adding support for reporting in your
teal
module.
Using standard widgets in your custom module
The teal.widgets
package provides various widgets which can be leveraged to quickly
create standard elements in your custom module.