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

如何在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]}")

关键说明

  1. 全局状态管理:用st.session_state.x_range统一存储X轴范围,确保两个图表使用相同的基准。
  2. 避免循环重渲染:在回调函数中添加范围差异判断,仅当新范围与当前范围有明显差异时才更新状态,防止两个图表互相触发回调导致无限重渲染。
  3. 强制应用范围:生成每个图表时,通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 10:47:05