如何在Streamlit中联动两个独立Plotly图表的X轴(缩放/平移)
如何在Streamlit中实现两个独立Plotly图表的X轴联动?
我需要在Streamlit中用两个独立的Plotly图表可视化音频数据:一个为时域波形图,另一个为MFCC(梅尔频率倒谱系数)热力图,要求缩放或平移其中一个图表时,另一个的X轴能同步更新。目前的代码可生成两个图表,但无法实现联动;此前尝试用st.session_state存储X轴范围并通过on_change回调更新的方法不稳定,常出现同步失效或重渲染错误。
现有基础代码
import streamlit as st import librosa import numpy as np import plotly.graph_objects as go st.title("Audio Plots") # Load Librosa example audio audio, sr = librosa.load(librosa.ex('trumpet')) duration = len(audio) / sr time_axis = np.linspace(0, duration, len(audio)) # Compute MFCCs mfccs = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=20) mfcc_time = librosa.frames_to_time(np.arange(mfccs.shape[1]), sr=sr) # Time Domain plot fig1 = go.Figure() fig1.add_trace(go.Scatter(x=time_axis, y=audio, line=dict(color="#4169E1"), name="Time Domain")) fig1.update_layout(xaxis_title="Time (s)", yaxis_title="Amplitude", title="Time Domain", height=300) st.plotly_chart(fig1, use_container_width=True) # MFCC plot fig2 = go.Figure() fig2.add_trace(go.Heatmap(z=mfccs, x=mfcc_time, y=np.arange(1, 21), colorscale="Viridis", name="MFCC")) fig2.update_layout(xaxis_title="Time (s)", yaxis_title="Coefficient", title="MFCC", height=300) st.plotly_chart(fig2, use_container_width=True) st.write(f"Duration: {duration:.2f}s, Samples: {len(audio)}, MFCC Frames: {mfccs.shape[1]}")
已尝试的方法
- 使用
plotly.subplots.make_subplots(shared_xaxes=True):可实现X轴联动,但无法满足独立图表的布局灵活性需求。 - 通过
st.session_state存储X轴范围+on_change回调:偶尔有效,但Streamlit重渲染时经常失效,同步不可靠。
可靠解决方案
核心思路是利用st.session_state存储全局X轴范围,为两个图表分别设置回调更新该范围,同时在生成图表时强制应用当前范围,并且通过范围判断避免循环重渲染。
修改后的完整代码:
import streamlit as st import librosa import numpy as np import plotly.graph_objects as go st.title("Audio Plots") # Load Librosa example audio audio, sr = librosa.load(librosa.ex('trumpet')) duration = len(audio) / sr time_axis = np.linspace(0, duration, len(audio)) # Compute MFCCs mfccs = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=20) mfcc_time = librosa.frames_to_time(np.arange(mfccs.shape[1]), sr=sr) # 初始化全局X轴范围 if "x_range" not in st.session_state: st.session_state.x_range = [0, duration] def update_x_range(change): """更新全局X轴范围,避免重复设置导致循环重渲染""" if not change: return # 提取新的X轴范围 new_range = [change.get("xaxis.range[0]"), change.get("xaxis.range[1]")] # 仅当新范围与当前范围差异显著时更新 if (new_range[0] is not None and new_range[1] is not None) and \ (abs(new_range[0] - st.session_state.x_range[0]) > 0.01 or abs(new_range[1] - st.session_state.x_range[1]) > 0.01): st.session_state.x_range = new_range # 时域波形图:应用全局X轴范围并绑定回调 fig1 = go.Figure() fig1.add_trace(go.Scatter(x=time_axis, y=audio, line=dict(color="#4169E1"), name="Time Domain")) fig1.update_layout( xaxis_title="Time (s)", yaxis_title="Amplitude", title="Time Domain", height=300, xaxis=dict(range=st.session_state.x_range) # 应用当前X轴范围 ) st.plotly_chart(fig1, use_container_width=True, key="time_plot", on_change=update_x_range) # MFCC热力图:应用全局X轴范围并绑定回调 fig2 = go.Figure() fig2.add_trace(go.Heatmap(z=mfccs, x=mfcc_time, y=np.arange(1, 21), colorscale="Viridis", name="MFCC")) fig2.update_layout( xaxis_title="Time (s)", yaxis_title="Coefficient", title="MFCC", height=300, xaxis=dict(range=st.session_state.x_range) # 应用当前X轴范围 ) st.plotly_chart(fig2, use_container_width=True, key="mfcc_plot", on_change=update_x_range) st.write(f"Duration: {duration:.2f}s, Samples: {len(audio)}, MFCC Frames: {mfccs.shape[1]}")
关键说明
- 全局状态管理:用
st.session_state.x_range统一存储X轴范围,确保两个图表使用相同的基准。 - 避免循环重渲染:在回调函数中添加范围差异判断,仅当新范围与当前范围有明显差异时才更新状态,防止两个图表互相触发回调导致无限重渲染。
- 强制应用范围:生成每个图表时,通过
xaxis=dict(range=st.session_state.x_range)强制应用当前全局范围,保证联动同步。
测试步骤
- 安装依赖:
pip install streamlit plotly librosa numpy - 将代码保存为
test_streamlit.py - 运行:
streamlit run test_streamlit.py
内容的提问来源于stack exchange,提问作者faith76
相关产品推荐
相关产品推荐

