Hybrid Execution: Mixing Pre-Rendered and Live Content

Combining Build-Time Data with Client-Side Interactivity

Learn how to create hybrid interactive documents that combine pre-rendered content with live, client-side code execution. This tutorial explains how to generate build-time data sources using OJS, import them into interactive code cells, and pass data between R and Python environments.

Tools
Author
Affiliation
Published

March 21, 2025

Keywords

hybrid interactive execution, live and pre-rendered code, interactive WebAssembly

Introduction

Hybrid execution is an advanced technique that allows you to optimize interactive documents by pre-rendering heavy computations at build-time while maintaining live, client-side interactivity. This approach improves document load times and responsiveness while still providing dynamic features to the user.



1. Overview

Interactive documents often require balancing performance and interactivity. By pre-computing data and outputs during the document build (using OJS data sources) and then passing that data to live code cells running in the browser via WebAssembly, you can achieve both speed and interactivity.

Key Benefits:

  • Efficiency: Heavy computations are performed once during build-time, reducing runtime overhead.
  • Interactivity: Users still enjoy responsive, live updates for parts of the document.
  • Resource Optimization: Minimizes redundant computations on the client side.

2. Build-Time OJS Data Sources

Quarto Live supports exporting data as OJS data sources. This means you can evaluate code at build time (e.g., in an R code block) and export the results for use in interactive cells later.

Example: Pre-Rendering a Data Source in R

Source:

```{r}
# Preview the ToothGrowth dataset
head(ToothGrowth)
# Export ToothGrowth as an OJS data source
ojs_define(ToothGrowth)
```

The ToothGrowth dataset is now available as an OJS variable:
   
```{ojs}
ToothGrowth
```

Output:

  • In R, the code displays a preview of the dataset and exports it.
  • In an OJS cell, the ToothGrowth variable is now accessible for further client-side processing.
# Preview the ToothGrowth dataset
head(ToothGrowth)
   len supp dose
1  4.2   VC  0.5
2 11.5   VC  0.5
3  7.3   VC  0.5
4  5.8   VC  0.5
5  6.4   VC  0.5
6 10.0   VC  0.5
# Export ToothGrowth as an OJS data source
ojs_define(ToothGrowth)

The ToothGrowth dataset is now available as an OJS variable:

ToothGrowth

3. Importing Build-Time Data into Live Code Cells

Once you have an OJS data source, you can import it back into an interactive R or Python code block using the input cell option. This allows you to further process or visualize pre-rendered data on the client side.

Example: Live Visualization Using Pre-Rendered Data

hybrid.qmd
```{webr}
#| input:
#|   - ToothGrowth
# Filter the ToothGrowth data for specific doses and create a plot
data_filtered <- ToothGrowth |> dplyr::filter(dose %in% c(0.5, 2))
boxplot(len ~ dose, data = data_filtered)
head(data_filtered)
```
Note

In this example, the pre-rendered ToothGrowth dataset is imported back into the R interactive cell for further analysis.

4. Passing Data Between Engines

The hybrid approach also enables communication between different WebAssembly engines (e.g., between R and Python). Data processed in one engine can be passed to another, ensuring consistency and seamless interactivity.

Example: Sharing Data from R to Python

Source:

```{webr}
#| edit: false
#| define:
#|   - mpg
# Process and export a subset of the mtcars dataset
mpg <- mtcars |>
  dplyr::select(mpg, hp) |>
  dplyr::filter(mpg < 25)
```

```{pyodide}
#| edit: false
#| input:
#|   - mpg
# Import the exported data in Python and plot it
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame(mpg)
plt.plot(df['mpg'], df['hp'], 'o')
plt.title("MPG vs HP")
plt.xlabel("Miles Per Gallon")
plt.ylabel("Horsepower")
plt.show()
```

Output:

  • In the R block, the mpg dataset is processed and exported.
  • In the Python block, the mpg dataset is imported and visualized, demonstrating cross-engine data passing.

5. Best Practices for Hybrid Execution

  • Separate Static and Interactive Blocks:
    Clearly separate build-time computations (which can be cached) from live interactive code cells.

  • Use OJS Variables Effectively:
    Define OJS data sources for heavy data processing, and then import these sources into interactive cells for visualization and further analysis.

  • Optimize Performance:
    Pre-render as much static content as possible, and limit live computations to what’s necessary for user interactivity.

  • Test Across Environments:
    Verify that data passes correctly between R and Python blocks, and that both pre-rendered and live elements function as expected.

Further Reading

Conclusion

Hybrid execution allows you to harness the best of both worlds: pre-rendered, static outputs for heavy computations and live interactive elements for dynamic exploration. By transferring data through OJS variables and passing data between different WebAssembly engines, you can build efficient, highly responsive interactive documents. Experiment with these techniques to create rich, interactive content that performs well and scales easily.

Back to top

Reuse

Citation

BibTeX citation:
@online{kassambara2025,
  author = {Kassambara, Alboukadel},
  title = {Hybrid {Execution:} {Mixing} {Pre-Rendered} and {Live}
    {Content}},
  date = {2025-03-21},
  url = {https://www.datanovia.com/learn/interactive/advanced/hybrid-execution.html},
  langid = {en}
}
For attribution, please cite this work as:
Kassambara, Alboukadel. 2025. “Hybrid Execution: Mixing Pre-Rendered and Live Content.” March 21, 2025. https://www.datanovia.com/learn/interactive/advanced/hybrid-execution.html.