如何通过Ajax在Flask中更新Bokeh图表(保留页面其他元素)
Flask+Bokeh同步更新:解决图表与表格联动问题
我来帮你搞定这个问题,先快速解决当前的核心需求(让图表和表格一起更新),再实现你想要的两个优化点——不用重绘整个图表、不需要额外模板。
一、快速解决:替换现有图表组件
你后端已经能生成新的Bokeh组件了,只需要在前端把旧的图表替换掉就行,步骤很简单:
1. 给图表容器加固定ID(修改index.html)
把原来渲染Bokeh的部分改成带固定ID的容器,方便后续定位替换:
<!-- 替换原来的{{ div_bok }} --> <div id="bokeh-plot-wrapper"> {{ div_bok }} </div> {{ script_bok }}
2. 前端AJAX回调里替换图表(添加JS逻辑)
假设你用jQuery处理表单提交,在index.html里加这段脚本(记得先引入jQuery):
$(document).ready(function() { $('#input_data_form').submit(function(e) { e.preventDefault(); const nrow = $('#nrow').val(); const ncol = $('#ncol').val(); $.getJSON('/_get_table', {nrow, ncol}, function(res) { // 先更新表格(保留你原来的表格更新逻辑,比如DataTables的代码) $('#your-table-id').DataTable({ destroy: true, data: res.my_table, columns: res.columns }); // 然后更新Bokeh图表:替换容器内容+执行新脚本 $('#bokeh-plot-wrapper').html(res.div_bok); // 先删掉旧的Bokeh脚本避免冲突,再插入新脚本 $('script[id^="bokeh-"]').remove(); const newScript = document.createElement('script'); newScript.id = `bokeh-${Date.now()}`; newScript.innerHTML = res.script_bok; document.body.appendChild(newScript); }); }); });
3. 后端代码暂时不用改
你当前的get_table视图已经返回了div_bok和script_bok,这部分先保留就行。
二、优化升级:仅更新图表数据(不重绘整个图表)
上面的方法能解决问题,但每次都重新生成整个图表组件有点浪费性能。更好的方式是用Bokeh的ColumnDataSource来做数据驱动更新,只传新数据,不用重新生成组件。
1. 后端初始化用ColumnDataSource(修改index视图)
把静态数据换成可更新的数据源,还要把数据源ID传给前端:
from bokeh.models import ColumnDataSource @app.route('/') def index(): # 初始化可更新的数据源 source = ColumnDataSource(data=dict(x=[1,2,3], y=[1,2,3])) p = figure(plot_width=150, plot_height=100) p.line('x', 'y', source=source) # 绑定数据源到线条 script_bok, div_bok = components(p) return render_template('index.html', div_bok=div_bok, script_bok=script_bok, source_id=source.id) # 把数据源ID传给前端
2. 后端接口只返回数据(修改get_table视图)
现在不需要生成新的图表组件,只返回表格数据和图表的新数据:
@app.route('/_get_table') def get_table(): nrow = request.args.get('nrow', type=int) ncol = request.args.get('ncol', type=int) # 生成表格数据(保留你原来的逻辑) df = pd.DataFrame(np.random.randint(0, 10, size=(nrow, ncol))) table_json = json.loads(df.to_json(orient="split")) # 生成图表需要的新数据(这里可以换成你实际的业务逻辑) new_plot_data = dict( x=list(range(nrow)), y=list(range(nrow)) ) return jsonify( my_table=table_json["data"], columns=[{"title": str(col)} for col in table_json["columns"]], plot_data=new_plot_data )
3. 前端用Bokeh API更新数据(修改JS逻辑)
利用Bokeh的JS API直接更新数据源,图表会自动刷新,不用替换整个组件:
<!-- 确保你已经引入了Bokeh的JS文件,你原来的head里已经有了 --> <script src="http://cdn.bokeh.org/bokeh/release/bokeh-0.12.15.min.js"></script> <script src="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.15.min.js"></script> <script> $(document).ready(function() { // 拿到后端传过来的数据源ID const sourceId = "{{ source_id }}"; // 获取Bokeh文档里的数据源对象 const dataSource = Bokeh.documents[0].get_model_by_id(sourceId); $('#input_data_form').submit(function(e) { e.preventDefault(); const nrow = $('#nrow').val(); const ncol = $('#ncol').val(); $.getJSON('/_get_table', {nrow, ncol}, function(res) { // 更新表格(保留你的原有逻辑) $('#your-table-id').DataTable({ destroy: true, data: res.my_table, columns: res.columns }); // 只更新图表数据,图表自动刷新! dataSource.data = res.plot_data; dataSource.change.emit(); // 触发数据更新事件 }); }); }); </script>
几个小提示
- 注意Bokeh的JS版本要和后端版本一致(你用的是0.12.15,所以JS文件也要对应这个版本)。
- 优化方案里,图表的样式、尺寸都不会变,只会更新数据,完全满足你的需求,而且不需要额外模板。
- 如果你的Bokeh版本比较新,获取文档的方式可能是
Bokeh.documents[0]或者Bokeh.embed.get_document(),不过0.12.15用前者没问题。
这样就能完美实现表格和图表的同步更新啦!
内容的提问来源于stack exchange,提问作者Cleb
相关产品推荐
相关产品推荐

