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

Streamlit中Bokeh散点图点击回调无法获取点击值问题

问题:Streamlit 结合 Bokeh 点击散点图无法获取点击数据

使用 Streamlit 框架搭配 Bokeh 2.4.3 版本时,点击散点图中的点后,后端定义的 clicked_data 字典始终无法获取到点击的数值,仅能正常显示图表。相关代码如下:

import streamlit as st
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS

# Create a Streamlit web app
st.title("Interactive Scatter Plot")

# Create some example data
data = {
    "x": [1, 2, 3, 4, 5],
    "y": [5, 4, 3, 2, 1],
}

# Create a Bokeh figure and add a scatter plot
p = figure(title="Click on a dot", tools="tap,reset")
source = ColumnDataSource(data=data)
scatter = p.circle("x", "y", size=20, source=source)

# Create a JavaScript callback function
callback_code = """
    const selected_indices = cb_obj.indices;
    if (selected_indices.length > 0) {
        const selected_index = selected_indices[0];
        const x_value = source.data['x'][selected_index];
        const y_value = source.data['y'][selected_index];
        
        clicked_data.x = x_value;
        clicked_data.y = y_value;
    }
"""
clicked_data = {"x": None, "y": None}
callback = CustomJS(
    args={"source": source, "clicked_data": clicked_data}, code=callback_code
)

# Attach the JavaScript callback to the scatter plot
scatter.js_on_event("tap", callback)


# Display the Bokeh plot in Streamlit
st.bokeh_chart(p, use_container_width=True)

st.write(clicked_data)

问题原因

核心矛盾在于前端 JavaScript 与后端 Python 的运行环境完全分离:

  • CustomJS 是在浏览器端执行的代码,传入的 clicked_data 会被转换为前端的 JavaScript 对象,修改的只是前端对象的值,无法直接同步回后端的 Python 字典。
  • Streamlit 采用会话驱动的运行模式,每次页面交互都会重新执行整个 Python 脚本,后端的 clicked_data 会被重新初始化为 {"x": None, "y": None},自然无法保留前端修改的值。

解决方案

方案1:前端直接显示点击结果(无需同步到后端)

如果只需要在页面上展示点击数据,可借助 Bokeh 的 Div 组件在前端直接更新内容,无需同步到 Python 后端:

import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CustomJS, Div

st.title("Interactive Scatter Plot")

data = {
    "x": [1, 2, 3, 4, 5],
    "y": [5, 4, 3, 2, 1],
}

p = figure(title="Click on a dot", tools="tap,reset")
source = ColumnDataSource(data=data)
scatter = p.circle("x", "y", size=20, source=source)

# 创建用于展示结果的Div组件
result_div = Div(text="点击一个点查看数据")

callback_code = """
    const selected_indices = cb_obj.indices;
    if (selected_indices.length > 0) {
        const selected_index = selected_indices[0];
        const x_value = source.data['x'][selected_index];
        const y_value = source.data['y'][selected_index];
        
        // 更新Div的文本内容
        result_div.text = `点击的数据:x=${x_value}, y=${y_value}`;
    } else {
        result_div.text = '点击一个点查看数据';
    }
"""

callback = CustomJS(
    args={"source": source, "result_div": result_div}, code=callback_code
)

scatter.js_on_event("tap", callback)

# 在Streamlit中展示图表和结果
st.bokeh_chart(p, use_container_width=True)
st.bokeh_chart(result_div)

方案2:同步点击数据到 Python 后端

若需要将点击数据同步到后端用于后续逻辑处理,可通过 Streamlit 的组件消息机制,将前端数据传递回后端并更新会话状态:

import streamlit as st
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.embed import components

st.title("Interactive Scatter Plot")

# 初始化会话状态,用于存储点击数据
if 'clicked_data' not in st.session_state:
    st.session_state.clicked_data = {"x": None, "y": None}

data = {
    "x": [1, 2, 3, 4, 5],
    "y": [5, 4, 3, 2, 1],
}

p = figure(title="Click on a dot", tools="tap,reset")
source = ColumnDataSource(data=data)
scatter = p.circle("x", "y", size=20, source=source)

# 编写CustomJS,通过消息传递将数据发送到Streamlit后端
callback_code = """
    const selected_indices = cb_obj.indices;
    if (selected_indices.length > 0) {
        const selected_index = selected_indices[0];
        const x_value = source.data['x'][selected_index];
        const y_value = source.data['y'][selected_index];
        
        // 发送消息到Streamlit组件
        window.parent.postMessage({
            type: 'streamlit:setComponentValue',
            value: {x: x_value, y: y_value}
        }, '*');
    }
"""

callback = CustomJS(args={"source": source}, code=callback_code)
scatter.js_on_event("tap", callback)

# 将Bokeh图表转换为HTML组件
script, div = components(p)

# 自定义HTML,添加消息监听以接收并传递数据到Streamlit
html_content = f"""
{div}
{script}
<script>
window.addEventListener('message', (event) => {{
    if (event.data.type === 'streamlit:setComponentValue') {{
        // 将数据传递给Streamlit组件
        Streamlit.setComponentValue(event.data.value);
    }}
}});
</script>
"""

# 嵌入HTML组件并获取返回的点击数据
returned_data = st.components.v1.html(
    html_content,
    height=400,
    width=700,
    key="bokeh_interactive_plot"
)

# 更新会话状态中的点击数据
if returned_data:
    st.session_state.clicked_data = returned_data

# 展示后端获取到的点击数据
st.write("后端获取的点击数据:", st.session_state.clicked_data)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:32:18