Flask + Bokeh AjaxDataSource

12

如何在Flask和Bokeh中使用AjaxDataSource:

我有一个返回JSON数据的函数:

@app.route("/data", methods=['POST'])
def get_x():
    global x, y
    x = x + 0.1
    y = math.sin(x)
    return flask.jsonify(x=[x], y=[y])

我可以毫不费力地在Bokeh的AjaxDataSource中使用它来创建实时流图:

source = AjaxDataSource(data_url="http://localhost:5000/data", polling_interval=1000, mode='append')
p = figure()
p.line('x', 'y', source=source)                                                                       
show(p)

但是,当我尝试将其嵌入到Flask页面中时,AjaxDataSource无法查询服务器。图表无法呈现且没有错误。请注意,如果我使用静态图而不是AjaxDataSource,则可以正常绘制。以下是相关代码:

template = Template('''<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Streaming Example</title>
        {{ js_resources }}
        {{ css_resources }}
    </head>
    <body>
    {{ plot_div }}
    {{ plot_script }}
    </body>
</html>
''')

@app.route("/")
def simple():
    streaming=True
    source = AjaxDataSource(data_url="http://localhost:5000/data", 
                            polling_interval=1000, mode='append')

    fig = figure(title="Streaming Example")
    fig.line( 'x', 'y', source=source)

    js_resources = INLINE.render_js()
    css_resources = INLINE.render_css()

    script, div = components(fig, INLINE)

    html = template.render(
        plot_script=script,
        plot_div=div,
        js_resources=js_resources,
        css_resources=css_resources
    )

    return encode_utf8(html) 

如果有任何想法,我将不胜感激。

Brian


我想补充一点,在bokeh仓库中有一个官方示例:https://github.com/bokeh/bokeh/blob/1.2.0/examples/howto/ajax_source.py。 - ostrokach
2个回答

13

首先,作为一个友好的建议,请始终发布完整的可运行代码示例。花费了几分钟来重现所有缺失的必要导入内容,而仅需几秒钟即可在有可运行脚本的情况下进行诊断。


更新:自 Bokeh 0.12.15 版本以来,不应需要下面描述的解决方法。 AjaxDataSource 应该能够将数据流到空的 CDS(ColumnDataSource)中,无需提前创建空列。


最近,BokehJS 的一些代码路径变得更加“严格”,这在几乎每个实例中都是好的,但似乎这留下了与 AjaxDataSource 的不良交互没有被注意到。就我运行的示例而言,我确实在 浏览器 的 JS 控制台中看到了一个错误:

Error: attempted to retrieve property array for nonexistent field 'x'

这就是解决方法的关键,只需确保数据源具有(空)列xy即可:

source.data = dict(x=[], y=[])

下面是一个完整的可工作脚本。我请求您在Bokeh问题跟踪器上发布此信息,以使这个bug得到优先处理和修复。


from flask import Flask, jsonify
from jinja2 import Template
import math

from bokeh.plotting import figure
from bokeh.models import AjaxDataSource
from bokeh.embed import components
from bokeh.resources import INLINE


app = Flask(__name__)

x, y = 0, 0

@app.route("/data", methods=['POST'])
def get_x():
    global x, y
    x = x + 0.1
    y = math.sin(x)
    return jsonify(x=[x], y=[y])

template = Template('''<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Streaming Example</title>
        {{ js_resources }}
        {{ css_resources }}
    </head>
    <body>
    {{ plot_div }}
    {{ plot_script }}
    </body>
</html>
''')

@app.route("/")
def simple():
    streaming=True
    source = AjaxDataSource(data_url="http://localhost:5000/data",
                            polling_interval=1000, mode='append')

    source.data = dict(x=[], y=[])

    fig = figure(title="Streaming Example")
    fig.line( 'x', 'y', source=source)

    js_resources = INLINE.render_js()
    css_resources = INLINE.render_css()

    script, div = components(fig, INLINE)

    html = template.render(
        plot_script=script,
        plot_div=div,
        js_resources=js_resources,
        css_resources=css_resources
    )

    return html

app.run(debug=True)

非常感谢。这个完美地解决了问题。我会像你建议的那样发布一个问题。Brian。 - flailingsquirrel
我想使用render_template,并使用jinja2模板的html文件。我的html文件如下所示:{% extends "template.html" %}{% block content %}<div class="container"><div>{{ script | safe}}{{ div | safe}}</div></div>{% endblock %}。当我将返回编码(html)替换为return render_template('testajax.html',script = script,div = div)时,我遇到了这个错误:WindowsError:[Error 32]由于正在被另一个进程使用,因此无法访问该文件。有没有办法解决这个问题? - Hamid K
这似乎不是Bokeh的问题。也许您想要写入的文件在另一个进程中打开,比如编辑器? - bigreddot

8
与 OP 相同,我也想在 Bokeh 和 Flask 中使用 AJAX。然而,我不想通过 AjaxDataSource 不断地从服务器实时流式传输数据,而是希望仅当用户与网页上的输入交互时才从服务器获取新数据。为了实现这一目的,我以 bigreddot 的 答案 作为基础,将 AjaxDataSource 更改为 ColumnDataSource 并在 CustomJS 中添加了一个 jQuery AJAX 调用(下面的示例是使用 Python 3.6.4、Flask 1.0.2 和 Bokeh 0.13.0 创建的):
import json

from flask import Flask, jsonify, request
from jinja2 import Template
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.embed import components
from bokeh.resources import INLINE
from bokeh.layouts import column
from bokeh.util.string import encode_utf8

app = Flask(__name__)

N_DATAPOINTS = 20
DEFAULT_VARIABLE = 'bar'
MY_DATABASE = {
    'foo': [i**1 for i in range(N_DATAPOINTS)],
    'bar': [i**2 for i in range(N_DATAPOINTS)],
    'baz': [i**3 for i in range(N_DATAPOINTS)]}


@app.route("/get_new_data", methods=['POST'])
def get_new_data():
    app.logger.info(
        "Browser sent the following via AJAX: %s", json.dumps(request.form))
    variable_to_return = request.form['please_return_data_of_this_variable']
    return jsonify({variable_to_return: MY_DATABASE[variable_to_return]})


SIMPLE_HTML_TEMPLATE = Template('''
<!DOCTYPE html>
<html>
    <head>
        <script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
        {{ js_resources }}
        {{ css_resources }}
    </head>
    <body>
    {{ plot_div }}
    {{ plot_script }}
    </body>
</html>
''')


@app.route("/")
def simple():
    x = range(N_DATAPOINTS)
    y = MY_DATABASE[DEFAULT_VARIABLE]

    source = ColumnDataSource(data=dict(x=x, y=y))

    plot = figure(title="Flask + JQuery AJAX in Bokeh CustomJS")
    plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
    callback = CustomJS(args=dict(source=source), code="""
    var selected_value = cb_obj.value;
    var plot_data = source.data;

    jQuery.ajax({
        type: 'POST',
        url: '/get_new_data',
        data: {"please_return_data_of_this_variable": selected_value},
        dataType: 'json',
        success: function (json_from_server) {
            // alert(JSON.stringify(json_from_server));
            plot_data.y = json_from_server[selected_value];
            source.change.emit();
        },
        error: function() {
            alert("Oh no, something went wrong. Search for an error " +
                  "message in Flask log and browser developer tools.");
        }
    });
    """)

    select = Select(title="Select variable to visualize",
                    value=DEFAULT_VARIABLE,
                    options=list(MY_DATABASE.keys()),
                    callback=callback)

    layout = column(select, plot)
    script, div = components(layout)
    html = SIMPLE_HTML_TEMPLATE.render(
        plot_script=script,
        plot_div=div,
        js_resources=INLINE.render_js(),
        css_resources=INLINE.render_css())

    return encode_utf8(html)

app.run(debug=True, host="127.0.0.1", port=5002)

非常感谢您的代码。您是否知道是否有可能使用常规HTML选择功能替换内置的bokeh Select?在这种情况下如何实现ajax调用?通常,ajax调用是从.js文件中进行的,而不是在flask应用程序内部进行的。我正在尝试构建一个交互式的flask应用程序,避免使用内置小部件和bokeh服务器。有什么想法吗?谢谢 - chemist
非常愉快!不幸的是,我不知道如何替换内置的Bokeh选择框。我也想知道如何做到这一点,因为我想使用可搜索的选择框,例如Select2,因为我开发的应用程序之一包含了一个具有数百个项目的选择框,这使得滚动长列表以查找所需项目非常痛苦。如果您能解决这个问题,请告诉我 :) 祝你好运! - tuomastik
谢谢您的回复。如果我找到解决方案,我一定会告诉您。但目前我真的很怀疑这是可能的。祝你好运! - chemist
1
谢谢,我想这正是我一直在寻找的。 - jjepsuomi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接