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

Bokeh按钮点击事件更新地理可视化失败问题排查

问题分析与修复方案

我帮你排查了代码里的几个核心问题,这些就是点击按钮后地图毫无变化的原因:

1. 错误地重新创建整个地图对象,而非更新现有内容

你在callback函数里重新创建了p = gmap(...),然后调用curdoc().clear()清空文档再添加新的地图。但这样做有两个问题:

  • 原来的布局里绑定的是旧的p对象,新创建的p和界面上显示的不是同一个引用,所以界面不会同步更新;
  • curdoc().clear()会把之前添加的按钮也一起清空,后续再添加column(button, p)相当于重新构建界面,逻辑混乱。

正确的做法是复用已有的地图对象,只更新它的数据源和内容,而不是从头创建新地图。

2. 错误添加了与地图坐标系不匹配的测试点

你在callback里添加了这些代码:

x = np.linspace(0, 4 * np.pi, 100)
y = np.sin(x)
p.circle(x, y, legend="Male", color="red")
p.circle(x, 2 * y, legend="Female", color="Blue")
p.circle(x, 3 * y, legend="Unknown", color="Green")

谷歌地图的坐标系是经纬度(经度范围大概是-180到180,纬度-90到90),但这里的x是0到12左右的数值,这些点会被渲染到地图的极远处,根本看不到,而且这也不是你需要的客户数据点,完全是多余的。

3. 没有正确更新数据源,而是重复创建

你每次都重新创建ColumnDataSource,但其实应该保留一个全局的数据源对象,在callback里只更新它的data属性,这样Bokeh会自动触发界面更新。

修复后的完整代码

from bokeh.plotting import gmap
from bokeh.io import curdoc
from bokeh.transform import factor_cmap
from bokeh.models.widgets import Button
from bokeh.layouts import column
from bokeh.models import GMapOptions, ColumnDataSource, CategoricalColorMapper
import pandas as pd
import pyodbc

# 数据库参数
server = 'x.x.x.x\abc'
db = 'abc'
# 创建数据库连接
conn = pyodbc.connect('DRIVER={SQL Server};SERVER=' + server + ';DATABASE=' + db + ';Trusted_Connection=yes')

# 初始化地图配置
map_options = GMapOptions(lat=37.686293, lng=-97.3614409, map_type="roadmap", zoom=13)
p = gmap("My_Google_Maps_API_Key", map_options, title="Client_Distribution", plot_width=1000, plot_height=600)

# 初始化空的数据源,后续更新数据用
source = ColumnDataSource(data=dict(latitude=[], longitude=[], gender=[]))

# 设置颜色映射
color_mapper = CategoricalColorMapper(factors=['M', 'F', 'U'], palette=['Red', 'Blue', 'Green'])

# 在地图上添加圆形标记,绑定数据源和颜色映射
p.circle(x="longitude", y="latitude", size=4, fill_alpha=0.9, 
         source=source, 
         fill_color={'field': 'gender', 'transform': color_mapper}, 
         line_color={'field': 'gender', 'transform': color_mapper})

def callback(event):
    # 执行SQL查询获取数据
    sql = """
    with CTE AS (
        SELECT CR.ClientID, CR.Latitude, CR.Longtitude 
        FROM Company C 
        LEFT JOIN ClientRegistration CR on C.CompanyID = CR.CompanyID 
        WHERE C.CompanyID = xxx AND (CR.Latitude IS NOT NULL AND CR.Longtitude IS NOT NULL)
    ) 
    SELECT C.ClientID, C.Latitude, C.Longtitude, CL.Sex 
    FROM CTE C 
    LEFT JOIN Clients CL on C.ClientID = CL.ClientID
    """
    df = pd.read_sql(sql, conn)
    
    # 数据类型转换
    df[['Latitude','Longtitude']] = df[['Latitude','Longtitude']].apply(pd.to_numeric)
    df['Sex'] = df['Sex'].astype('str')
    
    # 更新数据源的数据,这会自动触发地图的更新
    source.data = dict(
        latitude = df['Latitude'].tolist(),
        longitude = df['Longtitude'].tolist(),
        gender = df['Sex'].tolist()
    )
    print("数据已更新,当前客户数量:", len(df))

# 创建按钮并绑定回调函数
button = Button(label="加载客户性别分布")
button.on_event("button_click", callback)

# 构建布局并添加到文档
curdoc().add_root(column(button, p))

关键修复点说明

  • 提前初始化地图对象p和数据源source,并把圆形标记绑定到这个数据源上;
  • callback函数里只负责查询数据、处理数据格式,然后更新现有数据源的data属性,Bokeh会自动检测到数据源变化并刷新地图;
  • 移除了多余的正弦曲线测试点,避免干扰;
  • 去掉了混乱的curdoc().clear()和重复创建地图的逻辑,保持界面布局的一致性;
  • 简化了事件绑定,用字符串"button_click"代替ButtonClick事件类,代码更简洁。

现在点击按钮后,地图就会加载并显示按性别颜色区分的客户数据点了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:56:22