Skip to main content

Plotly

Control the Megane viewer from Plotly charts using ipywidgets event interop. Click a data point on a Plotly FigureWidget to jump to the corresponding trajectory frame.

Click to Select Frame​

import plotly.graph_objects as go
import ipywidgets as widgets
import megane

viewer = megane.view_traj("protein.pdb", xtc="trajectory.xtc")
frames = list(range(viewer.total_frames))
energy = ... # one value per frame, e.g. from your simulation log

# Create a Plotly time-series chart, plus a marker for the current frame
fig = go.FigureWidget(
data=[
go.Scatter(x=frames, y=energy, mode="lines+markers", name="Energy"),
go.Scatter(x=[0], y=[energy[0]], mode="markers", marker=dict(size=12, color="red"), name="Current"),
],
layout=go.Layout(
xaxis_title="Frame", yaxis_title="Energy (kJ/mol)",
),
)

# Plotly click → Megane frame selection
def on_click(trace, points, state):
if points.point_inds:
viewer.frame_index = points.point_inds[0]

fig.data[0].on_click(on_click)

# Display side by side
widgets.VBox([fig, viewer])

Sync Plotly Marker with Current Frame​

Use Megane's on_event("frame_change") callback to update a marker on the Plotly chart:

# Megane → Plotly (update red marker on the chart)
marker_trace = fig.data[1]

@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]]

Dihedral Trajectory Analysis​

Collect dihedral angles across all frames and plot the result. The viewer computes measurements in the browser, and the kernel cannot receive them while a cell is still running, so a loop that sets frame_index and waits for measurement events collects nothing. Compute the series from the trajectory in Python and let the viewer highlight the same atoms:

import numpy as np
from megane import load_trajectory

atoms = [10, 20, 30, 40]
viewer.selected_atoms = atoms # live dihedral in the viewer

def dihedral_deg(p0, p1, p2, p3):
b0, b1, b2 = p0 - p1, p2 - p1, p3 - p2
b1n = b1 / np.linalg.norm(b1)
v = b0 - np.dot(b0, b1n) * b1n
w = b2 - np.dot(b2, b1n) * b1n
return float(np.degrees(np.arctan2(np.dot(np.cross(b1n, v), w), np.dot(v, w))))

trajectory = load_trajectory("protein.pdb", "trajectory.xtc")
dihedrals = [
dihedral_deg(*trajectory.get_frame(i)[atoms]) for i in range(trajectory.n_frames)
]

# Plot with Plotly
fig = go.FigureWidget(
data=[go.Scatter(y=dihedrals, mode="lines", name="Dihedral")],
layout=go.Layout(xaxis_title="Frame", yaxis_title="Angle (°)"),
)
fig

See examples/external_events.ipynb for complete working examples.