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

