如何创建网络图中具有固定节点位置和线图中累积时间序列数据的同步 Plotly 子图?

问题描述 投票:0回答:1

我正在尝试使用 Plotly 构建一个带有两个子图的同步可视化:下面的网络图和上面的线图。网络图中的每个节点代表固定 2D 空间中的一个实体,其颜色强度表示其激活级别,该级别随时间变化。线图显示每个节点随时间变化的激活水平。我想使用滑块同时控制两个子图。

每个子图的要求是:

  1. 网络图(底部子图):

    • 每个节点代表 2D 空间中固定位置的一个实体(x 和 y 坐标恒定)。
    • 每个节点的颜色强度表示其激活级别,随着时间的推移而变化。
    • 移动滑块应仅根据所选时间更新每个节点的颜色强度。
  2. 线图(顶部子图):

    • 显示每个节点随时间的激活级别,x 轴代表时间,y 轴代表激活级别。
    • 在线图中每个节点都有恒定的颜色。
    • 随着滑块移动,线图应累积数据直至选定时间,有效显示截至该点的激活历史记录。

这是我的代码和到目前为止的绘图图像,它设置了两个子图并尝试用滑块控制它们。然而,我遇到了一个问题,即network graph节点(下面的子图)不会停留在固定位置;相反,x 轴也会在这里更新。

enter image description here

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'
}

# Create subplot layout with 2 rows: line plot (row 1) and network graph (row 2)
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.5, 0.5], vertical_spacing=0.15)

# Add line plot traces for each node with constant color, one scatter per time step
line_traces = {node: [] for node in nodes}
for node in nodes:
    for t in range(len(times)):
        trace = go.Scatter(
            x=times[:t+1],  # Show data only up to the current time
            y=activation_data[node][:t+1],
            mode='lines+markers',
            name=f'Node {node} Activation',
            line=dict(color=node_colors[node]),  # Apply constant color for each node
            visible=(t == 0)  # Initially only the first point is visible
        )
        fig.add_trace(trace, row=1, col=1)
        line_traces[node].append(trace)

# Add network graph traces, one for each time step
for t in range(len(times)):
    node_activation_colors = [activation_data[node][t] for node in nodes]
    fig.add_trace(
        go.Scatter(
            x=[1, 2, 3],  # Replace with actual node positions
            y=[1, 2, 3],
            mode='markers',
            marker=dict(size=20, color=node_activation_colors, colorscale='Viridis', showscale=False),  # Remove color scale
            name=f"Network at t={times[t]:.1f}",
            visible=(t == 0)  # Only first frame visible initially
        ),
        row=2, col=1
    )

# Define the slider steps
steps = []
for t in range(len(times)):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig.data)}],
        label=f"t = {times[t]:.1f}",
    )
    # Make all traces up to the current time `t` visible for the line plot (row 1)
    for node_idx, node in enumerate(nodes):
        for i in range(t + 1):
            step["args"][0]["visible"][node_idx * len(times) + i] = True

    # Enable only the current network graph trace in row 2
    step["args"][0]["visible"][len(nodes) * len(times) + t] = True
    steps.append(step)

# Add slider to control both plots
sliders = [dict(
    active=0,
    currentvalue={"prefix": "Time: "},
    pad={"t": 50},
    steps=steps
)]

# Update layout to remove legend and add slider
fig.update_layout(
    sliders=sliders,
    height=600,
    title="Synchronized Network Graph and Line Plot with Time Slider",
    showlegend=False  # Disable legend
)

fig.show()

此实现的问题是:

  • 网络图中的固定位置:如何确保网络图中节点的 x 和 y 位置保持不变,而仅根据每个时间步的激活进行颜色更新?
  • 线图中的累积数据:如何使线图累积数据直至滑块上的选定时间,以便显示截至该点的完整激活历史记录?
python plotly plotly.graph-objects
1个回答
0
投票

我找到了两个问题的解决方案,我想分享它们,以防其他人遇到类似问题。

  • 问题1:

    • 网络图中的固定位置问题:网络图中的节点没有停留在固定位置,因为 Plotly 根据可见数据自动调整轴范围。当数据更新时,轴会自行重新缩放。

    • 解决方案: 使用 update_xaxes 和 update_yaxes(fixedrange=True)设置网络图子图的固定轴范围并指定范围参数。

  • 问题2:

    • 线图中的累积数据 问题: 我最初为每个时间步长为每个节点创建多个轨迹,这使得控制可见性变得复杂,并且不允许线图累积数据直至选定的时间。

    • 解决方案:在线图中为每个节点创建一条轨迹,并使用 Plotly 的帧和动画功能随时间更新数据。在每一帧中,更新线图迹线的 x 和 y 数据以包含截至选定时间的数据。

更新代码

这是实现这些解决方案的更新代码:

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()
© www.soinside.com 2019 - 2024. All rights reserved.