Interactive 3D visualization slides with trame and Reveal.JS

August 25, 2026
Laptop displaying a Kitware OpenFOAM aerodynamic streamline simulation of a motorcycle rider emerging from the screen.

It can be difficult to show visualization results during slide presentations, leading to a choice between videos or images, lacking in interactivity and detail, or pausing the presentation and switching to a running application, breaking the flow of the presentation.

With trame and Reveal.js, you no longer have to choose between suboptimal choices. You can include interactive 3D renderings in your slides. Reveal.js allows creating slide presentations in HTML, while trame allows easily creating scientific visualization web applications in Python.

Reveal.js

Reveal.js is an open source HTML presentation framework that enables creating slide presentations. It is typically used for technical talks, workshops, docs, and interactive presentations where static slides are too limiting.

Under the hood, Reveal.js scans a simple HTML structure made of nested <section> elements and interprets it as a two-dimensional slide grid (horizontal and vertical). It then uses JavaScript to track the current position in that grid, listen for keyboard/touch input, update the URL for deep-linking, and fire lifecycle events as slides change. Rather than swapping pages, it positions slides in 3D space with CSS transforms and smoothly “moves the camera” between them. Optional plugins preprocess Markdown into slides and add features such as syntax highlighting or speaker notes.

Reveal.JS is notably used by Slides.com, a popular no-code and online slide presentation editor.

trame

trame lets you build interactive web apps entirely in Python by wiring a reactive state model to a Vue.js front end over a lightweight WebSocket bridge. The Python process owns the application state and heavy logic (including VTK/ParaView/Slicer pipelines), while trame automatically mirrors state changes to the browser UI and routes user events back to Python without having to write JavaScript code. UI layouts are declared with Vue components from Python, and trame handles session management, syncing, and rendering so scientific/visualization apps can run locally or remotely and stream results to the browser in real time.

By displaying a trame-based application in an iframe, RevealJS can seamlessly incorporate the interactive visualization in your slides to present your scientific results, as showcased in the following use cases.

Use case: Interactive tutorial

The video is an example of an interactive tutorial that showcases demo applications of a given toolkit (here trame-slicer, which brings 3D Slicer to the web with modern UI) along with the associated code. Multiple applications can be embedded in a single slide presentation.

Use case: Interactive results presentation

The video demonstrates how numerical simulation results can be interactively presented.
A unique trame server instance is running during the presentation. Showing a new slide changes the state (e.g. filter parameters, camera orientation, active field array…) of the trame application.

It is to be noted that trame can also be used to configure numerical simulations.

How it works

A trame server is running in the background (e.g. on a localhost with port 8080) while the index.html generated by RevealJS is opened in a browser. For each slide, the document contains a <section> that can contain an iframe with a URL to the served trame application. The URL can contain the information of what slide is currently visible (e.g. localhost:8080/step3).

The trame server then parses the URL parameters to load the correct application state when switching slides.

<section>
  <img style="position: fixed; top: 2%; right: 1%; height: 4%; margin: 0;" src="kitware.png" alt="My image">
  <h1>Title 1</h1>
  <h2>Subtitle 1</h2>
  <div class="split">
    <iframe data-src="http://localhost:8080/index.html"></iframe>
    <div class="panel">
      <pre><code class="python">
        ...
      </code></pre>
    </div>
  </div>
</section>

In this example, the trame application must be running on the 8080 port (default value). Such an application is usually launched by executing the Python file in which your TrameApp lives.

python ./app.py &
firefox ./index.html

You can use 3 different methods to display different content on each slide:

  • By serving multiple applications with a different port and referencing their respective URLs in the HTML file (localhost:8080, localhost:8081, localhost:8082…).
import subprocess
from pathlib import Path

FOLDER = Path("./trame-slicer")  # To update with your local trame-slicer directory
EXAMPLE_FOLDER = FOLDER.joinpath("examples/minimal")
PYTHON = FOLDER.joinpath(".venv/Scripts/python.exe")  # To update with your venv directory

BASE_PORT = 8080

if not PYTHON.exists():
    print("invalid python path")
else:
    files = sorted(EXAMPLE_FOLDER.glob("_0*.py"))

    for i, file in enumerate(files):
        port = BASE_PORT + i
        print(f"Launching {file.name} on port {port}")
        subprocess.Popen([PYTHON, str(file), "--server", "--port", str(port)])

input("Press Enter to exit...")

Python code snippet to launch all trame-slicer examples on different ports.

  • By parsing the provided URL (e.g. localhost:8080/index.html?step=3) on “client connection”. It can then be used to configure the UI and application state.
from trame.app import TrameApp
from trame.widgets import client


class App(TrameApp):
    def __init__(self):
        super().__init__()
        self.ctrl.on_client_connected.add(self.on_client_connected)
        self.state.search_parameters = ''
        ...  # State and data initialization
        self._build_ui()

    def _build_ui(self):
        with SinglePageLayout(self.server) as layout:
            self.ctrl.update_search_parameters = client.JSEval(
                exec=f'{self.state.search_parameters} = window.location.search;'
            ).exec
            ...  # Rest of UI definition

    def on_client_connected(self):
        self.ctrl.update_search_parameters()
        if self.data.search_parameters == "?step=":
            self.data.show_mesh = True
            self.data.show_openfoam_block = True
            self.data.show_streamlines = False
            self.render_view.Update()
            self.local_view.update()
        else:
            simple.Hide(self.mesh)
            simple.Hide(self.clip)
            self.data.show_streamlines = True
            self.render_view.Update()
            self.local_view.update()

Python code snippet to observe URL parameters

  • By using the routing: trame-router enables registering different UIs for different routes, which are defined in the URL of the page (e.g. localhost:8080/step1, localhost:8080/step2, localhost:8080/step3…)

Get started

You can find the RevealJS trame-slicer examples presentation as a downloadable GIST.

If you want to learn more about Reveal.js, trame, or trame-slicer, you can use the following links:

Contact Us

Leave a Reply