The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
The modules in VizModules are designed to be composed and extended. You can build higher-level modules that add custom logic such as data filtering, transformations, or additional UI controls while reusing the full functionality of the base modules.
This vignette demonstrates how to create a custom module by building
on top of the scatterPlot module.
When building a custom module, you need to handle Shiny’s namespacing correctly. The key insight is:
NS(id).id, not a namespaced version.moduleServer()
block.moduleServer() block to avoid
double-namespacing issues.Let’s build a custom module that adds a simple filtering checkbox to
the scatterPlot module.
library(VizModules)
minimalModuleUI <- function(id) {
ns <- NS(id)
tagList(
h4("Minimal Module Controls"),
# Custom input - uses the module's namespace
checkboxInput(ns("filter_setosa"), "Start with Setosa Only", value = FALSE),
hr(),
# Base module UI - pass the bare 'id', not ns(id)
dittoViz_scatterPlotInputsUI(id, iris)
)
}
minimalModuleOutput <- function(id) {
# Simply delegate to the base module's output UI
dittoViz_scatterPlotOutputUI(id)
}Notice that checkboxInput() uses
ns("filter_setosa") to namespace the custom input, while
dittoViz_scatterPlotInputsUI() receives the bare
id. This ensures the base module creates its inputs in the
correct namespace.
minimalModuleServer <- function(id, data_reactive) {
# Step 1: Process data inside a moduleServer block
# This gives us access to inputs namespaced to 'id' (our module's inputs)
filtered_data <- moduleServer(id, function(input, output, session) {
reactive({
req(data_reactive())
df <- data_reactive()
# Input specific to this custom module
if (isTRUE(input$filter_setosa)) {
if ("Species" %in% names(df)) {
df <- df[df$Species == "setosa", ]
}
}
df
})
})
# Step 2: Call the base module server OUTSIDE the moduleServer block
# This is critical! If we called this inside the moduleServer above,
# dittoViz_scatterPlotServer would look for inputs at id-id-inputName instead of id-inputName
dittoViz_scatterPlotServer(id, filtered_data)
}Why this pattern?
moduleServer(id, ...) gives us access to
input$filter_setosa, which is namespaced to our wrapper’s
id.dittoViz_scatterPlotServer(id, filtered_data)
outside the moduleServer() closure, the base
module attaches to the same namespace as our UI, not a nested one.dittoViz_scatterPlotServer() inside the
moduleServer() block, it would create nested namespaces
like id-id-x_axis, which wouldn’t match the actual input
IDs in the UI.ui <- fluidPage(
titlePanel("Minimal Module Example"),
sidebarLayout(
sidebarPanel(
minimalModuleUI("demo")
),
mainPanel(
minimalModuleOutput("demo")
)
)
)
server <- function(input, output, session) {
# Pass a reactive data source to the module
minimalModuleServer("demo", reactive({
iris
}))
}
shinyApp(ui, server)If your module pre-sets certain parameters, you can hide those inputs from the user to keep them from being changed:
The base modules let users interactively drag and edit the plot title, legend, annotations, draggable axis titles, and continuous-colour legend (colorbar), and they re-apply those manual tweaks across re-renders. When you wrap a base module (as in the examples above), this persistence is inherited for free.
If you build a brand-new plotly output from scratch
inside a custom module (rather than delegating to a base module’s
server), you can add the same behaviour simply with 3 basic steps:
customPlotServer <- function(id, data_reactive) {
moduleServer(id, function(input, output, session) {
# 1. A unique event source + the edit store, created once.
plot_source <- session$ns("customplot")
edit_store <- setup_manual_edits(input, session, plot_source)
output$plot <- renderPlotly({
# 2. Create your plotly object with your plotting function
fig <- build_my_plotly_figure(data_reactive(), input)
# 3. Restore + capture edits on every render, then return the figure.
finalize_manual_edits(fig, plot_source, edit_store, session)
})
})
}See the “Adding a New Module” vignette for the full description of these helpers, but this is pretty much all you need to do.
Keep wrapper logic focused: Each wrapper should add a cohesive set of related functionality.
Document the data requirements: If your wrapper expects certain columns, a specific class or data structure, etc, document this clearly.
Use reactive expressions: Use reactive data inputs.
Test the namespace: If inputs aren’t working, check that you’re handling namespaces correctly. A common symptom of namespace issues is that inputs seem to have no effect.
Consider composability: Design your wrappers so they could potentially be wrapped by even higher-level modules.
See any of the existing modules for clear reference examples.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.