Flask + Bokeh AjaxDataSource

后端 未结 2 1566
忘了有多久
忘了有多久 2020-12-10 14:04

Struggling with Flask + Bokeh AjaxDataSource:

I have a function that returns json data:

@app.route(\"/data\", methods=[\'POST\'])
def get_x():
    gl         


        
相关标签:
2条回答
  • 2020-12-10 14:15

    Like the OP, I also wanted to use AJAX with Bokeh and Flask. However, instead of continuously streaming the data from the server with AjaxDataSource I wanted to get the new data from the server only when user interacts with the inputs on the web page. To accomplish this, I used bigreddot's answer as a basis, changed AjaxDataSource to ColumnDataSource and added a jQuery AJAX call inside CustomJS (the following example has been created with Python 3.6.4, Flask 1.0.2 and 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)
    
    0 讨论(0)
  • 2020-12-10 14:32

    First, as a gentle suggestion, please always post complete runnable code examples. It took a few minutes to reproduce all the missing necessary imports, for something that only took seconds to diagnose once there was a runnable script.


    UPDATE: Since Bokeh 0.12.15 the workaround described below should not be required. AjaxDataSource should stream without complaint into an empty CDS with no empty columns created up front.


    Recently some of the BokehJS code paths were made more "strict" which is good in almost every instance, but it appears this left a bad interaction with AjaxDataSource that was not noticed. FWIW when I run example I do see an error in the browser JS console:

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

    And this is the key to the workround, which is just to make sure the data source does have (empty) columns for x and y:

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

    There is a complete working script below. I'd ask that you please make an issue on the Bokeh issue tracker with this information so that this bug can be prioritized and fixed


    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
    from bokeh.util.string import encode_utf8
    
    
    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 encode_utf8(html)
    
    app.run(debug=True)
    
    0 讨论(0)
提交回复
热议问题