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
相关产品推荐
相关产品推荐

