Jupyter widget
Use megane inside Jupyter notebooks as an interactive widget backed by anywidget. The widget is the MolecularViewer object; megane.view() / view_traj() are convenience wrappers that return one. Works in JupyterLab, Jupyter Notebook, VS Code, and Google Colab.
Installation
pip install megane
Then launch JupyterLab (recommended) or classic Notebook:
jupyter lab
Quick Start
Display a structure
import megane
viewer = megane.view("caffeine.pdb")
viewer # display inline
The last expression in a cell renders the widget. Use display(viewer) to render it multiple times in the same notebook.
Play a trajectory
import megane
viewer = megane.view_traj("protein.pdb", xtc="trajectory.xtc", bonds="structure")
viewer
The timeline scrubber appears automatically when a trajectory is loaded.
Options
Both view() and view_traj() accept these keyword arguments:
| Parameter | Type | Default | Description |
|---|---|---|---|
bonds | "distance" | "structure" | None | "distance" | Bond detection method. None to hide bonds |
perspective | bool | False | Perspective projection instead of orthographic |
cell_axes_visible | bool | True | Show unit cell axes |
For full control over the visualization (filtering, multi-layer rendering, custom nodes), see Visual Pipeline — Python API.
Widget API
After displaying the widget, control it programmatically:
| Property / Method | Type | Description |
|---|---|---|
viewer.frame_index | int (read/write) | Current trajectory frame (0-based) |
viewer.total_frames | int (read) | Total number of frames |
viewer.selected_atoms | list[int] (read/write) | Indices of currently selected atoms |
viewer.measurement | dict | None (read) | Current measurement result |
viewer.set_pipeline(pipe) | method | Apply a pipeline to the viewer |
viewer.on_event(name, fn) | method | Register an event callback |
viewer.off_event(name, fn) | method | Remove an event callback |
Example — jump to a specific frame:
viewer.frame_index = 100
Example — read the current measurement:
viewer.selected_atoms = [10, 20, 30, 40] # select 4 atoms
print(viewer.measurement)
# {"type": "dihedral", "value": 120.5, "label": "120.5°", "atoms": [10, 20, 30, 40]}
Event Callbacks
React to user interactions with on_event():
Available events
| Event | Fires when | Payload |
|---|---|---|
frame_change | Frame index changes | {"frame_index": int} |
selection_change | Selected atoms change | {"atoms": list[int]} |
measurement | User selects 2–4 atoms | {"type": str, "value": float, "label": str, "atoms": list[int]} or None |
Registering callbacks
Use on_event() as a decorator or method call:
# Decorator style
@viewer.on_event("frame_change")
def on_frame(data):
print(f"Frame changed to {data['frame_index']}")
# Method-call style
def on_select(data):
print(f"Selected: {data['atoms']}")
viewer.on_event("selection_change", on_select)
Removing callbacks
viewer.off_event("frame_change", on_frame) # remove a specific callback
viewer.off_event("selection_change") # remove all callbacks for this event
Plotly Integration
Combine megane with Plotly FigureWidget to build interactive analysis dashboards.
Click a chart point to jump to a frame
import plotly.graph_objects as go
import ipywidgets as widgets
import megane
viewer = megane.view_traj("protein.pdb", xtc="trajectory.xtc")
fig = go.FigureWidget(
data=[go.Scatter(x=list(range(n_frames)), y=energy, mode="lines+markers")],
layout=go.Layout(xaxis_title="Frame", yaxis_title="Energy (kJ/mol)"),
)
def on_click(trace, points, state):
if points.point_inds:
viewer.frame_index = points.point_inds[0]
fig.data[0].on_click(on_click)
widgets.VBox([fig, viewer])
Sync a Plotly marker with the current frame
@viewer.on_event("frame_change")
def update_marker(data):
idx = data["frame_index"]
with fig.batch_update():
marker_trace.x = [idx]
marker_trace.y = [energy[idx]]
Collect dihedral angles across all frames
import time
viewer.selected_atoms = [10, 20, 30, 40]
dihedrals = []
@viewer.on_event("measurement")
def collect(data):
if data and data["type"] == "dihedral":
dihedrals.append(data["value"])
for i in range(viewer.total_frames):
viewer.frame_index = i
time.sleep(0.01) # allow widget sync
viewer.off_event("measurement", collect)
# Plot the result
fig = go.FigureWidget(
data=[go.Scatter(y=dihedrals, mode="lines")],
layout=go.Layout(xaxis_title="Frame", yaxis_title="Dihedral (°)"),
)
fig
Interactive Demos
The following are actual executed notebooks you can browse inline.
Demo Notebook
Basic usage — loading structures, navigating trajectories, atom selection, measurement, and side-by-side viewers.
Advanced: External Events
Event handling, Plotly integration, bidirectional sync, dihedral analysis, and custom widget composition.
Pipeline API Reference
For simple visualization, use megane.view() and megane.view_traj(). For advanced customization (filtering, multi-layer rendering, custom nodes), build a pipeline manually with the Pipeline class. See Visual Pipeline — Python API for the complete list of node classes, port names, and parameters.
All classes and convenience functions are importable from megane:
from megane import (
view, view_traj, # convenience wrappers
Pipeline, # pipeline builder
LoadStructure, LoadTrajectory, LoadVector, Streaming, # source nodes
Filter, Modify, Color, Representation, # processing nodes
AddBonds, AddLabels, AddPolyhedra, # overlay nodes
VectorOverlay, Viewport, # display nodes
)
Quick Reference
Mouse Controls
| Action | Effect |
|---|---|
| Left-drag | Rotate |
| Scroll / Pinch | Zoom |
| Right-click atom | Select atom |
| Double-click | Center on atom |
Select 2–4 atoms to measure distances, angles, or dihedral angles.
Measurement Types
| Atoms Selected | Measurement |
|---|---|
| 2 | Distance (Å) |
| 3 | Angle (°) |
| 4 | Dihedral angle (°) |
API Reference
Methods
| Method | Description |
|---|---|
set_pipeline(pipeline) | Apply a Pipeline to the viewer, or None to clear |
on_event(name, callback=None) | Register an event callback. Use as @viewer.on_event("name") decorator or viewer.on_event("name", fn) |
off_event(name, callback=None) | Remove a specific callback, or all callbacks for the event if callback is None |
Properties
| Property | Type | Read/Write | Description |
|---|---|---|---|
frame_index | int | R/W | Current trajectory frame (0-based). Setting this fires frame_change |
total_frames | int | R | Total number of frames in the loaded trajectory |
selected_atoms | list[int] | R/W | Indices of selected atoms. Setting this fires selection_change |
measurement | dict | None | R | Current measurement result, or None if fewer than 2 atoms selected |
The measurement dict contains: type ("distance", "angle", or "dihedral"), value (float), label (formatted string like "120.5°"), and atoms (list of indices).
Tips
- Re-running cells — calling
viewer.set_pipeline(pipe)again updates the displayed pipeline in-place without re-creating the widget. - JupyterLab vs. classic Notebook — megane is tested on JupyterLab 4+. In classic Notebook, enable the
anywidgetextension if the viewer appears blank. - Large trajectories — the Jupyter widget loads the full trajectory into Python memory before scrubbing. If your XTC is too large, run
megane serve protein.pdb --xtc traj.xtcfrom the CLI instead — only the standalone server streams frames over WebSocket on demand. See Platform Support — UI features. - Sharing notebooks — exported HTML (
File → Export → HTML) captures the last-rendered frame as a static image.
Troubleshooting
VS Code: "Failed to load model class 'AnyModel' from module 'anywidget'"
This is a known issue with VS Code's Jupyter extension and affects many anywidget-based projects.
Workarounds:
-
Reload the VS Code window after installing megane:
- Open the Command Palette (
Ctrl+Shift+P/Cmd+Shift+P) - Run
Developer: Reload Window
- Open the Command Palette (
-
Ensure ipywidgets v8.x is installed:
pip install "ipywidgets>=8.0.0" -
Update the VS Code Jupyter extension to the latest version.
-
Restart the kernel and re-run the notebook cells.