2017-06-22 135 views
2

我需要在Web瀏覽器中以可視化的方式直觀顯示中的流式數據。唉,我對Javascript的知識是無效的,在測試之前我想知道一點點散景,但看起來我沒有。[Python + Bokeh + Flask]:Flask,嵌入多個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> 

任何幫助是值得歡迎的。

回答

2

如果有人有興趣的話,這裏是一個工作(非常原始的)例子,基於背景虛化/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>