[Python + Bokeh + Flask]:Flask,嵌入多个Bokeh图形,可视化实时流媒体

时间:2017-06-22 07:40:53

标签: python flask bokeh

我需要在网络浏览器上显示来自流数据的嵌入的多个图表,以便快速显示。我对Javascript的了解是空的,我想在此测试之前知道一点Bokeh,但看起来我没有。

我对你可以找到的几十个例子感到非常困惑,但是不能使用最后一个Bokeh库(0.12.6)。 已经看了Streaming two line graphs using bokeh,但是我需要使用Flask。

我帮助了解解决问题的正确方向,而不是重写我的代码。

到目前为止,我找到了一个解决方案,但它耗费了大量CPU(这是一个正常工作的代码,很抱歉:我已将其剥离了)。同样,它只刷新整个页面而不是图形。在Chromium 58(Linux)中没问题,在Firefox 53(Linux)中非常慢。

将Bokeh更改为另一个库是一种选择。烧瓶是强制性的。

app.py:

from bokeh.models import (FactorRange, LinearAxis, Grid, Range1d)
from bokeh.models.glyphs import Line
from bokeh.plotting import figure
from bokeh.embed import components
from bokeh.models.sources import ColumnDataSource

from flask import Flask, render_template

import random

app = Flask(__name__)


class SingleLine():
    def __init__(self, color, elem_number):

        self.color = color
        self.elem_number = elem_number

        self.data = {"time": [], "value": []}
        for i in range(1, elem_number + 1):
            self.data['time'].append(i)
            self.data['value'].append(random.randint(1,100))

    def create_line_plot(self, plot, x_name, y_name):
        source = ColumnDataSource(self.data)
        glyph = Line(x=x_name, y=y_name, line_color=self.color)
        plot.add_glyph(source, glyph)

#----------------------------------------------------------------------------------

class CompleteGraph():

    def __init__(self, lines_list, x_name, y_name, title, height=300, width=1000):
        self.lines_list = lines_list
        self.x_name = x_name
        self.y_name = y_name
        self.title = title
        self.height = height
        self.width = width

    def create_graph(self):

        xdr = FactorRange(factors=self.lines_list[0].data[self.x_name])
        ydr = Range1d(start=0, end=max(self.lines_list[0].data[self.y_name]) * 1.5)

        plot = figure(title=self.title, x_range=xdr, y_range=ydr,
                      plot_width=self.width, plot_height=self.height,
                      h_symmetry=False, v_symmetry=False,
                      tools=[],
                      responsive=True)

        for l in self.lines_list:
            l.create_line_plot(plot, self.x_name, self.y_name)

        xaxis = LinearAxis()
        yaxis = LinearAxis()

        plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
        plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))

        return components(plot)


@app.route("/")
def chart():
    elem_number = 30

    line1 = SingleLine(color="#ff0000", elem_number=elem_number)
    line2 = SingleLine(color="#00ff00", elem_number=elem_number)
    line3 = SingleLine(color="#00ff00", elem_number=elem_number)

    # first graph
    lines_list = [line1, line2]
    lg1 = CompleteGraph(lines_list, "time", "value", "title graph 1")

    # second graph
    lines_list = [line1, line3]
    lg2 = CompleteGraph(lines_list, "time", "value", "title graph 2")

    script1, div1 = lg1.create_graph()
    script2, div2 = lg2.create_graph()

    return render_template("test_stackoverflow.html",
                           div1=div1, script1=script1,
                           div2=div2, script2=script2,
                           )


if __name__ == "__main__":
    app.run(port=5000, debug=True)

和相应的模板:

test_stackoverflow.html

<html>
    <head>
        <style>
            #wrapper { display: flex; }
            #left { flex: 0 0 50%; }
            #right { flex: 1; }
            #wide { flex: 0 0 90% }
        </style>

        <title>Multiple realtime charts with Bokeh</title>
        <link href="http://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min" rel="stylesheet">
        <link href="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.css" rel="stylesheet">

        <script src="http://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.js"></script>
        <script src="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.js"></script>

        <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>

        <script>
            (function worker() {
                $.ajax({
                    url: '/',
                    success: function(data) {
                       location.reload();
                    },
                    complete: function() {
                       // Schedule the next request when complete
                       setTimeout(worker, 1000);
                    }
                });
            })();
        </script>
    </head>

    <body>
        <div id="wrapper">
            <div id="left">
                <h1>left graph</h1>
                {{ div1 | safe }}
                {{ script1 | safe }}
            </div>

            <div id="right">
                <h1>right graph</h1>
                {{ div2 | safe }}
                {{ script2 | safe }}
            </div>
        </div>
    </body>
</html>

欢迎任何帮助。

1 个答案:

答案 0 :(得分:3)

如果有人对它感兴趣,这里有一个工作(非常原始)的例子,基于bokeh /examples/howto/ajax_source.py

<强> app.py:

import numpy as np
from datetime import timedelta
from functools import update_wrapper, wraps
from math import sin, cos
from random import random
from six import string_types

from bokeh.plotting import figure
from bokeh.models.sources import AjaxDataSource
from bokeh.embed import components


from flask import Flask, jsonify, make_response, request, current_app, render_template


#########################################################
# Flask server related
#
# The following code has no relation to bokeh and it's only
# purpose is to serve data to the AjaxDataSource instantiated
# previously. Flask just happens to be one of the python
# web frameworks that makes it's easy and concise to do so
#########################################################


def crossdomain(origin=None, methods=None, headers=None,
                max_age=21600, attach_to_all=True,
                automatic_options=True):
    """
    Decorator to set crossdomain configuration on a Flask view

    For more details about it refer to:

    http://flask.pocoo.org/snippets/56/
    """
    if methods is not None:
        methods = ', '.join(sorted(x.upper() for x in methods))

    if headers is not None and not isinstance(headers, string_types):
        headers = ', '.join(x.upper() for x in headers)

    if not isinstance(origin, string_types):
        origin = ', '.join(origin)

    if isinstance(max_age, timedelta):
        max_age = max_age.total_seconds()

    def get_methods():
        options_resp = current_app.make_default_options_response()
        return options_resp.headers['allow']

    def decorator(f):
        @wraps(f)
        def wrapped_function(*args, **kwargs):
            if automatic_options and request.method == 'OPTIONS':
                resp = current_app.make_default_options_response()
            else:
                resp = make_response(f(*args, **kwargs))
            if not attach_to_all and request.method != 'OPTIONS':
                return resp

            h = resp.headers

            h['Access-Control-Allow-Origin'] = origin
            h['Access-Control-Allow-Methods'] = get_methods()
            h['Access-Control-Max-Age'] = str(max_age)
            requested_headers = request.headers.get(
                'Access-Control-Request-Headers'
            )
            if headers is not None:
                h['Access-Control-Allow-Headers'] = headers
            elif requested_headers:
                h['Access-Control-Allow-Headers'] = requested_headers
            return resp
        f.provide_automatic_options = False
        return update_wrapper(wrapped_function, f)

    return decorator




app = Flask(__name__)

x = list(np.arange(0, 6, 0.1))
y1 = [sin(xx) + random() for xx in x]
y2 = [sin(xx) + random() for xx in x]

@app.route('/data', methods=['GET', 'OPTIONS', 'POST'])
@crossdomain(origin="*", methods=['GET', 'POST'], headers=None)
def hello_world():
    x.append(x[-1]+0.1)
    y1.append(sin(x[-1])+random())
    y2.append(cos(x[-1])+random())
    return jsonify(x=x[-500:],
                    y1=y1[-500:],
                    y2=y2[-500:],
                )



@app.route("/")
def main_graph():

    source = AjaxDataSource(data=dict(x=[], y1=[], y2=[], y3=[]),
                            data_url='http://127.0.0.1:5050/data',
                            polling_interval=1000)

    p = figure()
    p.line(x='x', y='y1', source=source)

    p.x_range.follow = "end"
    p.x_range.follow_interval = 10

    script1, div1 = components(p)

    # -------------------------------------
    p = figure()
    p.line(x='x', y='y2', source=source)

    p.x_range.follow = "end"
    p.x_range.follow_interval = 10

    script2, div2 = components(p)

    return render_template("test_stackoverflow.html",
                           div1=div1, script1=script1,
                           div2=div2, script2=script2,
                           )

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5050, debug=True)

<强> test_stackoverflow.html:

<!DOCTYPE html>

<html>
    <head>
        <style>
            #wrapper { display: flex; }
            #left { flex: 0 0 50%; }
            #right { flex: 1; }
            #wide { flex: 0 0 90% }
        </style>

        <title>Multiple realtime charts with Bokeh</title>
        <link href="http://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min" rel="stylesheet">
        <link href="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.css" rel="stylesheet">

        <script src="http://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.js"></script>
        <script src="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.js"></script>

        <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>


    </head>

    <body>
        <div id="wrapper">
            <div id="left">
                <h1>left graph</h1>
                {{ div1 | safe }}
                {{ script1 | safe }}
            </div>

            <div id="right">
                <h1>right graph</h1>
                {{ div2 | safe }}
                {{ script2 | safe }}
            </div>
        </div>
    </body>
</html>