Building Interactive Widgets with RserveTS
widgets.RmdThe RserveTS package provides a powerful system for creating interactive widgets that maintain synchronized state between R and TypeScript/JavaScript applications. This vignette demonstrates how to build widgets with three example widgets: a plot control widget, a histogram widget, and a barplot widget.
Overview
Widgets in RserveTS are stateful objects that can be shared between R and TypeScript applications. They provide:
- Reactive properties: Properties that automatically synchronize between R and TypeScript
- Property handlers: Callbacks that trigger when properties change
- Hierarchical structure: Widgets can contain child widgets
- Type safety: Full TypeScript type definitions generated from R code
Example Widgets
The example application demonstrates three interconnected widgets
working with the iris dataset:
-
Plot Control Widget (
CtrlWidget): A controller widget that coordinates the histogram and barchart widgets -
Histogram Widget (
HistogramWidget): Displays a histogram of a selected variable with interactive bar selection -
Barplot Widget (
BarchartWidget): Shows species counts that update based on row selection
Widget Definitions
Histogram Widget
The histogram widget allows users to select a variable and number of bins, and displays the resulting histogram. It also supports interactive selection of histogram bars to filter data.
histogramWidget <- createWidget(
"HistogramWidget",
properties = list(
vars = ts_character(0L), # Available variable names
var = ts_character(1L), # Currently selected variable
nBin = ts_integer(1L), # Number of bins
counts = ts_numeric(0L), # Histogram counts
selectedBar = ts_integer(1L), # Currently selected bar index
controller = "ANY" # Parent controller widget
),
initialize = function(widget, parent = NULL) {
# Assign parent to controller property
if (!is.null(parent)) {
widget$controller <- parent
}
# Initialize available variables (excluding Species)
allvars <- names(iris)
widget$set("vars", allvars[allvars != "Species"])
widget$set("var", "")
widget$set("nBin", 10L)
widget$set("selectedBar", 0L)
widget$set("counts", numeric(0))
# Initial update
widget$update()
# Set up property handlers for reactive updates
widget$addPropHandler("var", function() widget$update())
widget$addPropHandler("nBin", function() widget$update())
# Handle bar selection to filter data
if (!is.null(widget$controller)) {
widget$addPropHandler("selectedBar", function() {
x <- iris[[widget$var]]
if (widget$selectedBar < 0) {
widget$controller$set("selectedRows", seq_along(x))
return()
}
br <- hist(x,
seq(min(x), max(x), length.out = widget$nBin + 1L),
plot = FALSE
)$breaks
i <- widget$selectedBar + 1
xx <- which(x >= br[i] & x < br[i + 1])
widget$controller$set("selectedRows", xx)
})
}
},
methods = list(
update = ts_function(
function() {
if (.self$var == "") {
return()
}
x <- iris[[.self$var]]
h <- hist(x,
seq(min(x), max(x), length.out = .self$nBin + 1L),
plot = FALSE
)
# Update the front end
.self$set("counts", as.numeric(h$counts))
# Push changes through to UI
.self$updateState()
}
)
),
export = TRUE
)Key features:
- Properties are defined using
ts_*()functions to specify TypeScript types - The
initializefunction sets up initial state and property handlers - Property handlers (
addPropHandler) trigger callbacks when properties change - The
update()method recalculates the histogram and synchronizes state -
updateState()pushes changes to the TypeScript frontend
Barplot Widget
The barplot widget displays species counts and updates based on row filtering from the controller.
barchartWidget <- createWidget(
"BarchartWidget",
properties = list(
data = ts_dataframe(label = ts_character(0L), count = ts_integer(0L)),
subset = ts_integer() # Row indices to include
),
initialize = function(widget) {
widget$set("subset", seq_len(nrow(iris)))
widget$update()
widget$addPropHandler("subset", function() widget$update())
},
methods = list(
update = ts_function(
function() {
tbl <- xtabs(~Species, data = iris[.self$subset, ]) |>
as.data.frame() |>
setNames(c("label", "count"))
.self$set("data", tbl)
.self$updateState()
}
)
),
export = TRUE
)Key features:
- Uses
ts_dataframe()to define structured data with typed columns - Automatically updates when the
subsetproperty changes - The
update()method recalculates counts for the filtered data
Plot Control Widget
The control widget coordinates the histogram and barplot widgets, managing row selection.
ctrlWidget <- createWidget(
"CtrlWidget",
properties = list(
selectedRows = ts_integer(0L), # Selected row indices
histogram = histogramWidget, # Child histogram widget
barchart = barchartWidget # Child barplot widget
),
initialize = function(widget) {
widget$set("selectedBin", 0)
# When rows are selected, update the barplot
widget$addPropHandler("selectedRows", function() {
widget$barchart$set("subset", widget$selectedRows)
})
},
methods = list(
histogram = ts_function(function() {
})
),
export = TRUE
)Key features:
- Demonstrates hierarchical widgets: child widgets are included as properties
- The histogram widget is initialized with the controller as its parent
- Property handlers coordinate between widgets
Compiling and Deploying
Once you’ve defined your widgets, you need to:
- Compile the R code to generate TypeScript schemas
- Deploy the Rserve server to run the widgets
Compiling
Use ts_compile() to generate both the TypeScript schema
and the Rserve deployment script:
# Compile widgets.R to generate:
# - widgets.rserve.ts (TypeScript schema)
# - widgets.rserve.R (Rserve deployment script)
ts_compile("widgets.R", filename = "widgets.rserve")The ts_compile() function:
- Reads your R file and identifies exported widgets (those with
export = TRUE) - Generates TypeScript type definitions using Zod schemas
- Creates an Rserve deployment script using
ts_deploy()
Deploying
The generated widgets.rserve.R file contains everything
needed to run the Rserve server. You can deploy it using:
# Option 1: Generate the deployment script and run manually
ts_compile("widgets.R", filename = "widgets.rserve")
Rscript widgets.rserve.R
# Option 2: Use ts_deploy() directly (generates and optionally runs)
ts_deploy("widgets.R", file = "widgets.rserve.R", run = "no")
Rscript widgets.rserve.RThe deployment script:
- Loads required libraries (
RserveandRserveTS) - Sources your widget definitions
- Exports widgets via the
ts_app()function - Starts an Rserve instance on port 6311 (configurable) with WebSocket support
Complete Example
Here’s a complete workflow:
library(RserveTS)
# 1. Define your widgets in widgets.R
# (See widget definitions above)
# 2. Compile to generate TypeScript and Rserve files
ts_compile("widgets.R", filename = "widgets.rserve")
# 3. Deploy the Rserve server
# In a terminal:
# Rscript widgets.rserve.RCreating a React Application
To create a React application that uses these widgets, you’ll need to:
- Set up a React project (e.g., using Vite)
- Install dependencies:
rserve-ts,@tmelliott/react-rserve, andzod - Import the generated TypeScript schema
- Use the
useRserveanduseWidgethooks from@tmelliott/react-rserve
You can see a live demo of these widgets working together at:
https://rserve-widgets-demo.up.railway.app/
The demo showcases all three widgets:
- Select a variable and adjust bins in the histogram
- Click histogram bars to filter the data
- See the barplot update automatically based on the selection
Summary
Widgets in RserveTS provide a powerful way to build interactive applications with synchronized state between R and TypeScript.
Key concepts:
- Properties: Define widget state with TypeScript types
- Initialize: Set up initial state and property handlers
- Methods: Define functions that can be called from TypeScript
- Property Handlers: React to property changes automatically
- Child Widgets: Build hierarchical widget structures
-
Compilation: Generate TypeScript schemas with
ts_compile() -
Deployment: Run Rserve server with
Rscripton the generated.rserve.Rfile
For more details on creating a React application that uses these
widgets, including example code and integration patterns, see the
documentation for the @tmelliott/react-rserve package.
Note: A detailed section on creating a React application with these widgets will be added in a future update to this vignette.