I found solutions to both of my problems, and I'd like to share them in case others face similar issues.
Problem 1:
Fixed Positions in Network Graph Issue: The nodes in the network graph were not staying in fixed positions because Plotly automatically adjusts the axis ranges based on the visible data. When the data updates, the axes rescale themselves.
Solution: Set fixed axis ranges for the network graph subplot using update_xaxes and update_yaxes with fixedrange=True and specify the range parameter.
Problem 2:
Cumulative Data in Line Plot Issue: I was initially creating multiple traces per node for each time step, which made controlling visibility complicated and didn't allow the line plot to accumulate data up to the selected time.
Solution: Create a single trace per node in the line plot and use Plotly's frames and animation features to update the data over time. In each frame, update the x and y data of the line plot traces to include data up to the selected time.
Updated Code
Here's the updated code that implements these solutions:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
# Example activation levels
times = np.arange(0, 10, 0.5)
nodes = ['A', 'B', 'C']
activation_data = {
'A': np.sin(times),
'B': np.cos(times),
'C': np.sin(times + np.pi / 4)
}
# Define fixed colors for each node
node_colors = {
'A': 'red',
'B': 'blue',
'C': 'green'
}
# Node positions
node_positions = {
'A': (1, 1),
'B': (2, 2),
'C': (3, 1.5)
}
x_positions = [node_positions[node][0] for node in nodes]
y_positions = [node_positions[node][1] for node in nodes]
# Create subplot layout
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=False,
row_heights=[0.5, 0.5],
vertical_spacing=0.15
)
# Add line plot traces for each node
for node in nodes:
fig.add_trace(
go.Scatter(
x=[], # Empty initial data
y=[],
mode='lines+markers',
name=f'Node {node} Activation',
line=dict(color=node_colors[node]),
),
row=1, col=1
)
# Add network graph trace
initial_colors = [activation_data[node][0] for node in nodes]
fig.add_trace(
go.Scatter(
x=x_positions,
y=y_positions,
mode='markers',
marker=dict(
size=20,
color=initial_colors,
colorscale='Viridis',
showscale=False
),
name="Network Graph",
),
row=2, col=1
)
# Fix axes ranges for network graph
fig.update_xaxes(
range=[0, 4],
fixedrange=True,
row=2, col=1
)
fig.update_yaxes(
range=[0, 3],
fixedrange=True,
row=2, col=1
)
# Create frames for animation
frames = []
for t, time in enumerate(times):
frame_data = []
# Update line plot traces
for i, node in enumerate(nodes):
x_data = times[:t+1]
y_data = activation_data[node][:t+1]
frame_data.append(go.Scatter(
x=x_data,
y=y_data,
mode='lines+markers',
line=dict(color=node_colors[node])
))
# Update network graph colors
node_activation_colors = [activation_data[node][t] for node in nodes]
frame_data.append(go.Scatter(
x=x_positions,
y=y_positions,
mode='markers',
marker=dict(
size=20,
color=node_activation_colors,
colorscale='Viridis',
showscale=False
)
))
frames.append(go.Frame(data=frame_data, name=str(t)))
# Define slider steps
sliders = [dict(
steps=[
dict(
method='animate',
args=(
[str(k)],
dict(
mode='immediate',
frame=dict(duration=0, redraw=True),
transition=dict(duration=0)
)
),
label=f"t={times[k]:.1f}"
) for k in range(len(times))
],
transition=dict(duration=0),
x=0, y=0,
currentvalue=dict(
font=dict(size=12),
prefix='Time: ',
visible=True,
xanchor='center'
),
len=1.0
)]
# Update layout with frames and slider
fig.update(frames=frames)
fig.update_layout(
sliders=sliders,
height=600,
title="Synchronized Network Graph and Line Plot with Time Slider",
)
# Fix axes ranges for line plot
fig.update_xaxes(
range=[times[0], times[-1]],
row=1, col=1
)
fig.show()