Skip to main content

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.

A live megane widget showing caffeine solvated in water — drag to rotate, scroll to zoom. This is the same renderer the notebook cell mounts.

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:

ParameterTypeDefaultDescription
bonds"distance" | "structure" | None"distance"Bond detection method. None to hide bonds
perspectiveboolFalsePerspective projection instead of orthographic
cell_axes_visibleboolTrueShow 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 / MethodTypeDescription
viewer.frame_indexint (read/write)Current trajectory frame (0-based)
viewer.total_framesint (read)Total number of frames
viewer.selected_atomslist[int] (read/write)Indices of currently selected atoms
viewer.measurementdict | None (read)Current measurement result
viewer.set_pipeline(pipe)methodApply a pipeline to the viewer
viewer.on_event(name, fn)methodRegister an event callback
viewer.off_event(name, fn)methodRemove 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

EventFires whenPayload
frame_changeFrame index changes{"frame_index": int}
selection_changeSelected atoms change{"atoms": list[int]}
measurementUser 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.

Loading notebook...

Advanced: External Events

Event handling, Plotly integration, bidirectional sync, dihedral analysis, and custom widget composition.

Loading notebook...

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

ActionEffect
Left-dragRotate
Scroll / PinchZoom
Right-click atomSelect atom
Double-clickCenter on atom

Select 2–4 atoms to measure distances, angles, or dihedral angles.

Measurement Types

Atoms SelectedMeasurement
2Distance (Å)
3Angle (°)
4Dihedral angle (°)

API Reference

Methods

MethodDescription
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

PropertyTypeRead/WriteDescription
frame_indexintR/WCurrent trajectory frame (0-based). Setting this fires frame_change
total_framesintRTotal number of frames in the loaded trajectory
selected_atomslist[int]R/WIndices of selected atoms. Setting this fires selection_change
measurementdict | NoneRCurrent 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 anywidget extension 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.xtc from 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:

  1. Reload the VS Code window after installing megane:

    • Open the Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
    • Run Developer: Reload Window
  2. Ensure ipywidgets v8.x is installed:

    pip install "ipywidgets>=8.0.0"
  3. Update the VS Code Jupyter extension to the latest version.

  4. Restart the kernel and re-run the notebook cells.