You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python+Plotly双滑块联动筛选数据可视化的技术求助

双滑块可视化解决方案(Plotly+Matplotlib)

一、Plotly双滑块联动修正

原代码核心问题是滑块步骤生成逻辑错误,未实现Epoch滑块选中对应Epoch的所有帧,Frame滑块选中对应Frame的所有Epoch,最终取交集显示的需求。以下是修正后的完整代码:

import plotly.graph_objects as go
import numpy as np

# 数据处理部分保持不变
render_comparison_per_epoch_np = np.stack(self.render_comparison_per_epoch[1:])
render_comparison_per_epoch_np_one_channel = np.mean(render_comparison_per_epoch_np, axis=2)

dimensions = render_comparison_per_epoch_np_one_channel.shape
dimensions_epoch = dimensions[0]
dimensions_frame = dimensions[1]
total_traces = dimensions_epoch * dimensions_frame

fig = go.Figure()

# 添加所有热力图轨迹,初始全部隐藏
for epoch_idx in range(dimensions_epoch):
    for frame_idx in range(dimensions_frame):
        fig.add_trace(go.Heatmap(
            z=render_comparison_per_epoch_np_one_channel[epoch_idx, frame_idx, ::-1, :],
            colorscale="Viridis",
            showscale=False,
            visible=False,
            name=f"Epoch {epoch_idx}, Frame {frame_idx}"
        ))

# 默认显示第一个Epoch的第一个Frame
fig.data[0].visible = True

# 生成Epoch滑块步骤:标记对应Epoch的所有Frame为可见候选
steps_epoch = []
for epoch_idx in range(dimensions_epoch):
    visible = [False] * total_traces
    for frame_idx in range(dimensions_frame):
        trace_idx = epoch_idx * dimensions_frame + frame_idx
        visible[trace_idx] = True
    
    steps_epoch.append(dict(
        method="restyle",
        args=[{"visible": visible}],
        label=f"Epoch {epoch_idx}"
    ))

# 生成Frame滑块步骤:标记对应Frame的所有Epoch为可见候选
steps_frame = []
for frame_idx in range(dimensions_frame):
    visible = [False] * total_traces
    for epoch_idx in range(dimensions_epoch):
        trace_idx = epoch_idx * dimensions_frame + frame_idx
        visible[trace_idx] = True
    
    steps_frame.append(dict(
        method="restyle",
        args=[{"visible": visible}],
        label=f"Frame {frame_idx}"
    ))

# 配置双滑块布局
sliders = [
    dict(
        active=0,
        steps=steps_epoch,
        pad={"t": 50},
        currentvalue={"prefix": "Epoch: "}
    ),
    dict(
        active=0,
        steps=steps_frame,
        pad={"t": 150},
        currentvalue={"prefix": "Frame: "}
    )
]

fig.update_layout(
    sliders=sliders,
    title="Epoch & Frame Interactive Heatmap"
)

# 注入自定义JS实现滑块可见性交集逻辑
js_code = """
<script>
let lastEpochVisible = [];
let lastFrameVisible = [];

// 监听Epoch滑块变化
document.querySelectorAll('.slider-container')[0].addEventListener('input', function(e) {
    let visible = Plotly.restyle(fig, 'visible')[0];
    lastEpochVisible = visible;
    updateCombinedVisible();
});

// 监听Frame滑块变化
document.querySelectorAll('.slider-container')[1].addEventListener('input', function(e) {
    let visible = Plotly.restyle(fig, 'visible')[0];
    lastFrameVisible = visible;
    updateCombinedVisible();
});

// 计算两个滑块可见性的交集并更新
function updateCombinedVisible() {
    if (lastEpochVisible.length === 0 || lastFrameVisible.length === 0) return;
    let combined = lastEpochVisible.map((v, i) => v && lastFrameVisible[i]);
    Plotly.restyle(fig, 'visible', combined);
}

// 初始化状态
lastEpochVisible = Plotly.restyle(fig, 'visible')[0];
lastFrameVisible = lastEpochVisible;
</script>
"""

# 保存为带交互逻辑的HTML
with open("render_comparison.html", "w") as f:
    f.write(fig.to_html(include_plotlyjs='cdn'))
    f.write(js_code)

# wandb日志上传
wandb.log({"simulation_render_per_epoch": wandb.Html("render_comparison.html")})

关键修正点

  • 初始所有轨迹隐藏,仅默认显示第一个轨迹
  • 分别为两个滑块生成对应维度的可见性标记列表
  • 通过自定义JS监听滑块事件,计算两个可见性列表的交集,实现联动筛选

二、Matplotlib交互图表保存方案

Matplotlib原生plt.savefig仅支持静态图像,要保存带滑块的交互图表,可采用以下两种方案:

方案1:保存为交互式HTML(基于mpld3)

使用mpld3库将Matplotlib图表转换为可交互的HTML文件:

import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import mpld3

# 数据处理部分保持不变
# render_comparison_per_epoch_np_one_channel = ...
dimensions = render_comparison_per_epoch_np_one_channel.shape
dimensions_epoch = dimensions[0]
dimensions_frame = dimensions[1]

fig, ax = plt.subplots(1, 1)
l = plt.imshow(render_comparison_per_epoch_np_one_channel[0][0])
axepoch = plt.axes([0.25, 0.1, 0.65, 0.03])
axframe = plt.axes([0.25, 0.15, 0.65, 0.03])

slider_epoch = Slider(
    ax=axepoch,
    label="Epoch",
    valmin=0,
    valmax=dimensions_epoch - 1,
    valinit=0,
    valstep=1,
)
slider_frame = Slider(
    ax=axframe,
    label="Frame",
    valmin=0,
    valmax=dimensions_frame - 1,
    valinit=0,
    valstep=1,
)

def update(val):
    epoch = int(slider_epoch.val)
    frame = int(slider_frame.val)
    l.set_data(render_comparison_per_epoch_np_one_channel[epoch][frame])
    fig.canvas.draw_idle()

slider_epoch.on_changed(update)
slider_frame.on_changed(update)

# 保存为交互式HTML
mpld3.save_html(fig, "matplotlib_interactive.html")
plt.show()

方案2:生成动画视频

若无需交互滑块,仅需保存所有Epoch+Frame的序列,可使用matplotlib.animation生成视频:

import matplotlib.pyplot as plt
import matplotlib.animation as animation

# 数据处理部分保持不变
# render_comparison_per_epoch_np_one_channel = ...
dimensions = render_comparison_per_epoch_np_one_channel.shape
dimensions_epoch = dimensions[0]
dimensions_frame = dimensions[1]

fig, ax = plt.subplots(1, 1)
l = ax.imshow(render_comparison_per_epoch_np_one_channel[0][0])

def animate(frame_idx):
    epoch = frame_idx // dimensions_frame
    frame = frame_idx % dimensions_frame
    l.set_data(render_comparison_per_epoch_np_one_channel[epoch][frame])
    ax.set_title(f"Epoch {epoch}, Frame {frame}")
    return l,

# 生成所有Epoch+Frame的序列动画
total_frames = dimensions_epoch * dimensions_frame
ani = animation.FuncAnimation(fig, animate, frames=total_frames, interval=200, blit=True)

# 保存为MP4(需提前安装ffmpeg)
ani.save("epoch_frame_animation.mp4", writer='ffmpeg', dpi=100)
plt.show()

内容的提问来源于stack exchange,提问作者truvaking

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 21:05:17