Bokeh更新Span位置为何需调用source.change.emit,能否免该调用优化交互性能
Bokeh悬停高亮竖线的更新机制与性能优化
问题原理解析
- 手动触发变更事件的必要性
Bokeh 2.3.0版本的前端JS实现没有为模型属性赋值操作做全局代理监听,在JS回调上下文中直接修改模型属性值(如v_line.location = 新坐标),仅会更新模型内部存储的属性数值,不会自动通知关联的渲染层执行重绘操作,因此界面上不会出现任何可见变化。 - 原实现的性能问题根源
原有代码中调用source.change.emit()才能让竖线更新,属于对Bokeh事件绑定机制的误用:ColumnDataSource的change事件是全局绑定信号,所有依赖该source的渲染器、关联图表,只要收到该事件就会执行全量数据重绘流程,和source本身存储的数据是否发生变更没有关联。- 这种写法会让每次悬停交互都触发所有关联图表的全量重绘,数据量越大、关联图表越多,重绘开销越高,最终表现为悬停响应卡顿、跳帧。
优化实现方案
完全不需要触发source.change.emit()即可完成竖线更新,核心思路是触发Span模型自身的变更通知:
- 将自定义回调中的触发逻辑,从触发source变更替换为触发Span模型自身的变更事件
原回调代码:
优化后回调代码:v_line.location = cb_data['index'].line_indices[0]; source.change.emit();v_line.location = cb_data['index'].line_indices[0]; v_line.change.emit(); - 优化逻辑说明
Span作为独立的Bokeh模型,自身的change事件只会通知绑定该模型的渲染层(也就是那条高亮竖线)执行重绘,完全不会触发所有关联图表的全量数据重绘,从根源上消除不必要的重绘开销。
完整可运行代码
import pandas as pd from bokeh.plotting import figure, curdoc from bokeh.models import ColumnDataSource, HoverTool, CustomJS, Span from bokeh.layouts import layout plot1 = figure(plot_width=1000, plot_height=250) df = pd.DataFrame({"ID":[0, 1, 2, 3, 4, 5, 6, 7], "Value1":[0, 100, 200, 300, 400, 500, 600, 700], "Value2":[0, 1, 2, 4,8 , 16, 32, 64]}) source = ColumnDataSource(df) line = plot1.line(x='ID', y='Value1', source=source) circle = plot1.circle(x='ID', y='Value1', source=source) v_line = Span(location=2, dimension='height', line_color='green', line_dash='dashed', line_width=3) plot1.add_layout(v_line) # 仅修改回调中的触发逻辑 callback = CustomJS(args=dict(source=source, v_line=v_line), code=""" v_line.location = cb_data['index'].line_indices[0]; v_line.change.emit(); """) hover_tool_plot = HoverTool(mode='vline', line_policy='nearest', callback=callback, renderers=[line]) plot1.add_tools(hover_tool_plot) layout_ = layout([[plot1]]) curdoc().add_root(layout_)
补充说明
- Bokeh 3.x版本为所有模型属性赋值添加了Proxy自动监听机制,后续升级到3.x版本后,甚至不需要手动调用
v_line.change.emit(),直接给v_line.location赋值即可自动触发重绘。 - 该优化方案在Bokeh 2.3.0版本下完全兼容,不会影响其他交互逻辑。
内容的提问来源于stack exchange,提问作者Crysers
相关产品推荐
相关产品推荐

