Flask Socketio |使用由 Flask Executor 或 ThreadPoolExecutor 创建的后台任务更新和绘制图表

人气:472 发布:2022-10-16 标签: html python-3.x plotly chart.js flask-socketio

问题描述

我目前正在尝试在数据框的结果更新或数据发生任何变化时成功绘制条形图.

设置:

Flask-SocketIO==4.3.1python-engineio==3.13.2python-socketio==4.6.0

确保重新启动机器以免遇到错误.

Python 代码 - 保存在 D:\Projects\test_backgroundtask:

from flask import Flask, render_template, request将熊猫导入为 pdfrom flask_executor import Executor情节导入导入 plotly.graph_objs as go从flask_socketio 导入SocketIO,发出导入json全局 test_valapp = Flask(__name__)socketio = SocketIO(app)def create_plot(feature_importance):feature_importance=feature_importance.reset_index(drop=True)feature_importance=feature_importance.iloc[0:5]打印(特征_重要性)数据 = [去吧(x=feature_importance['Age'], # 将 x 指定为数据框列 'x'y=feature_importance['Name'],orientation='h')]graphJSON = json.dumps(data, cls=plotly.utils.PlotlyJSONEncoder)返回graphJSON@socketio.on(响应")def background_task_func():全局 test_val全局图socketio.sleep(10)数据 = {'姓名': ['汤姆', '约瑟夫', '克里什', '约翰'], '年龄': [20, 21, 19, 18]}test_val= pd.DataFrame(data)bar = create_plot(test_val)情节=条如果 test_val.shape[0]>1:打印(test_val)发射('响应输出',情节,广播=真)socketio.sleep(1)#return render_template('views/index_img_soc.html', plot=bar)@app.route('/', methods=['GET'])定义索引():全局图executor.submit(background_task_func)bar = create_plot(test_val)return render_template('views/index_img_soc.html', plot=bar)如果 __name__ == __main__":数据 ={'姓名':[],'年龄':[]}test_val= pd.DataFrame(data)执行者 = 执行者(应用程序)socketio.run(应用程序)

Html 代码(保存在 D:\Projects\test_backgroundtask\template\views):

<script src=https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script><div class="chart";id="条形图"><script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js";完整性=sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I="crossorigin=匿名"></script><script type="text/javascript";字符集=utf-8">var socket = io().connect('http://127.0.0.1:5000');socket.emit('响应')socket.on('response_output', 函数(receiving_data) {无功图 = {{绘图 |安全的}};Plotly.plot('bargraph',graphs,{});});

输出:

WebSocket 传输不可用.安装 eventlet 或 gevent 和 gevent-websocket 以提高性能.* 服务 Flask 应用程序flask_background_app_img_soc2";(懒加载)* 环境:生产警告:这是一个开发服务器.不要在生产部署中使用它.改用生产 WSGI 服务器.* 调试模式:关闭* 在 http://127.0.0.1:5000/上运行(按 CTRL+C 退出)127.0.0.1 - - [14/Jul/2021 11:45:01] GET/HTTP/1.1"200 -空数据帧列:[姓名、年龄]指数: []2c6e85bb23aa4c83af37bc005a08837b:发送数据包OPEN数据{'sid':'2c6e85bb23aa4c83af37bc005a08837b', 'upgrades': [], 'pingTimeout', '000val', '2c6e85bb23aa4c83af37bc005a08837b':}02c6e85bb23aa4c83af37bc005a08837b:发送数据包MESSAGE数据0127.0.0.1 - - [14/Jul/2021 11:45:03]GET/socket.io/?EIO=3&transport=polling&t=NgaPyfS HTTP/1.1"200 -2c6e85bb23aa4c83af37bc005a08837b:收到数据包MESSAGE数据2[响应"]接收到事件响应"来自 2c6e85bb23aa4c83af37bc005a08837b [/]127.0.0.1 - - [14/Jul/2021 11:45:03] POST/socket.io/?EIO=3&transport=polling&t=NgaPyf-&sid=2c6e85bb23aa4c83af37bc007b&1200 -姓名年龄0 汤姆 201 约瑟夫 212 克里什 19约翰三书 18姓名年龄0 汤姆 201 约瑟夫 212 克里什 19约翰三书 18发射事件response_output"对所有人 [/]2c6e85bb23aa4c83af37bc005a08837b:发送数据包MESSAGE数据2[response_output",[{\"orientation\":\h\",\x\":[20, 21, 8], \"y\": [\"Tom\", \"Joseph\", \"Krish\", \"John\"], \"type\": \"bar\"}]"]127.0.0.1 - - [14/Jul/2021 11:45:13] "GET/socket.io/?EIO=3&transport=polling&t=NgaPyg0&sid=2c6e85bb23aa4c83af37bc007b&18200 -姓名年龄0 汤姆 201 约瑟夫 212 克里什 19约翰三书 18姓名年龄0 汤姆 201 约瑟夫 212 克里什 19约翰三书 182c6e85bb23aa4c83af37bc005a08837b:收到包PING数据无2c6e85bb23aa4c83af37bc005a08837b:发送分组PONG数据无127.0.0.1 - - [14/Jul/2021 11:45:28] "GET/socket.io/?EIO=3&transport=polling&t=NgaP_9E&sid=2c6e85bb23aa4c83af37bc007b&1200 -127.0.0.1 - - [14/Jul/2021 11:45:28] "POST/socket.io/?EIO=3&transport=polling&t=NgaQ2md&sid=2c6e85bb23aa4c83af37bc007b&1200 -2c6e85bb23aa4c83af37bc005a08837b:收到包PING数据无2c6e85bb23aa4c83af37bc005a08837b:发送分组PONG数据无127.0.0.1 - - [14/Jul/2021 11:45:54] "GET/socket.io/?EIO=3&transport=polling&t=NgaQ2nA&sid=2c6e85bb23aa4c83af37bc005b&1200 -127.0.0.1 - - [14/Jul/2021 11:45:54] "POST/socket.io/?EIO=3&transport=polling&t=NgaQ93n&sid=2c6e85bb23aa4c83af37bc007b&1200 -2c6e85bb23aa4c83af37bc005a08837b:收到包PING数据无2c6e85bb23aa4c83af37bc005a08837b:发送分组PONG数据无127.0.0.1 - - [14/Jul/2021 11:46:20] "GET/socket.io/?EIO=3&transport=polling&t=NgaQ94a&sid=2c6e85bb23aa4c83af37bc005b&1200 -127.0.0.1 - - [14/Jul/2021 11:46:20]POST/socket.io/?EIO=3&transport=polling&t=NgaQFPv&sid=2c6e85bb23aa4c83af37bc007a08"HTTP18200 -

第一个输出,即使数据帧已更新.

重新加载页面后,页面为空白,然后显示正确的图表.

我想要达到的目标:

更新数据框时自动显示条形图无需重新加载页面.

解决方案

在html页面的function(receiving_data)中,使用变量plot代替receiving_data.当 render_template 被调用时,变量 plot 被初始化.通过套接字发送数据时,调用 return render_template('views/index_img_soc.html', plot=bar) 时无需传递变量 plot.

请参阅下面的示例脚本,了解如何使用套接字将数据发送到 html 脚本.

下面的脚本会在不重新加载页面的情况下自动更新条形图.

使用chart.js 而不是plotly 的代码.有更多的例子在 javascript 中绘制 chart.js 而不是 plotly.

更新的 Python 脚本:

随机导入从烧瓶导入烧瓶,render_template,会话,请求从flask_socketio 导入SocketIOfrom flask_executor import Executor将熊猫导入为 pd导入jsonapp = Flask(__name__)app.config['SECRET_KEY'] = '秘密!'socketio = SocketIO(app)线程 = 无@socketio.on(response_demo")def background_task_func():"如何将服务器生成的事件发送到客户端的示例.""socketio.sleep(5)打印(发送")数据 = {'姓名':['汤姆','约瑟夫','克里什','约翰','沙兹'],'年龄':[20, 21, 19, 18,36]}data_2= pd.DataFrame(data)df_json=data_2.to_json(orient='记录')结果 = {对象":json.loads(df_json)}socketio.emit('my_response',result, broadcast=True)@app.route('/')定义索引():executor.submit(background_task_func)return render_template('index_2.html')如果 __name__ == '__main__':执行者 = 执行者(应用程序)socketio.run(应用程序)

更新的 html 脚本:

<头><title>Flask-SocketIO 测试</title><script type="text/javascript";src="//code.jquery.com/jquery-1.4.2.min.js"></script><script type="text/javascript";src=//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script><script type="text/javascript";src=https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script><script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js";完整性=sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I="crossorigin=匿名"></script><canvas id="myChart";宽度=100"height=100"</canvas><script type="text/javascript";字符集=utf-8">//Chart.js 条形图var ctx = document.getElementById(myChart");var myChart = 新图表(ctx,{类型:'酒吧',数据: {标签:[红色"、蓝色"、黄色"、绿色"、紫色"]、数据集:[{数据:[2, 2,2, 2,2],背景颜色: ['rgba(255, 99, 132, 0.2)','rgba(54, 162, 235, 0.2)','rgba(255, 206, 86, 0.2)','rgba(75, 192, 192, 0.2)','RGBA(153, 102, 255, 0.2)'],边框颜色: ['RGBA(255,99,132,1)','rgba(54, 162, 235, 1)','rgba(255, 206, 86, 1)','rgba(75, 192, 192, 1)','rgba(153, 102, 255, 1)'],边框宽度:1}]},选项: {尺度:{y轴:[{刻度:{零开始:真}}]}}});$(document).ready(function() {var socket = io().connect('http://127.0.0.1:5000');socket.emit('response_demo')socket.on('my_response', function(obj) {for (var i=0; i < obj.objects.length; i++) {myChart.data.datasets[0].data[i] =obj.objects[i].Age;myChart.data.labels[i] =obj.objects[i].Name;}myChart.update();});});</html>

I am currently trying to successfully plot a bar chart when the results from the dataframe is updated or when there is any change in the data.

Setup:

Flask-SocketIO==4.3.1 python-engineio==3.13.2 python-socketio==4.6.0

Make sure you restart your machine to not encounter errors.

Python Code -saved in D:\Projects\test_backgroundtask:

from flask import Flask, render_template, request
import pandas as pd
from flask_executor import Executor
import plotly
import plotly.graph_objs as go
from flask_socketio import SocketIO, emit
import json

global test_val
app = Flask(__name__)
socketio = SocketIO(app)
def create_plot(feature_importance):
    feature_importance=feature_importance.reset_index(drop=True)
    feature_importance=feature_importance.iloc[0:5]
    print(feature_importance)

    data = [
        go.Bar(
            x=feature_importance['Age'], # assign x as the dataframe column 'x'
            y=feature_importance['Name'], orientation='h'
        )
    ]

    graphJSON = json.dumps(data, cls=plotly.utils.PlotlyJSONEncoder)

    return graphJSON

@socketio.on("response")
def background_task_func():
    global test_val
    global plot
    socketio.sleep(10)
    data = {'Name': ['Tom', 'Joseph', 'Krish', 'John'], 'Age': [20, 21, 19, 18]}  
    test_val= pd.DataFrame(data)
    bar = create_plot(test_val)
    plot=bar
    if test_val.shape[0]>1:
        print(test_val)
        emit('response_output',plot ,broadcast=True)
        socketio.sleep(1)
        #return render_template('views/index_img_soc.html', plot=bar)
    
@app.route('/', methods=['GET'])
def index():
    global plot
    executor.submit(background_task_func)
    bar = create_plot(test_val)
    
    return render_template('views/index_img_soc.html', plot=bar)



if __name__ == "__main__":
    data ={'Name': [], 'Age': []}  
    test_val= pd.DataFrame(data)   
    executor = Executor(app)
    socketio.run(app) 

Html Code (saved in D:\Projects\test_backgroundtask\template\views):

<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<div class="chart" id="bargraph">

    <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
        <script type="text/javascript" charset="utf-8">

            var socket = io().connect('http://127.0.0.1:5000');
            socket.emit('response')
            socket.on('response_output', function(receiving_data) {
              var graphs = {{plot | safe}};
              Plotly.plot('bargraph',graphs,{});
            });
    </script>
</div>

Output:

WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
 * Serving Flask app "flask_background_app_img_soc2" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [14/Jul/2021 11:45:01] "GET / HTTP/1.1" 200 -
Empty DataFrame
Columns: [Name, Age]
Index: []
2c6e85bb23aa4c83af37bc005a08837b: Sending packet OPEN data {'sid': '2c6e85bb23aa4c83af37bc005a08837b', 'upgrades': [], 'pingTimeout': 60000, 'pingInterval': 25000}
2c6e85bb23aa4c83af37bc005a08837b: Sending packet MESSAGE data 0
127.0.0.1 - - [14/Jul/2021 11:45:03] "GET /socket.io/?EIO=3&transport=polling&t=NgaPyfS HTTP/1.1" 200 -
2c6e85bb23aa4c83af37bc005a08837b: Received packet MESSAGE data 2["response"]
received event "response" from 2c6e85bb23aa4c83af37bc005a08837b [/]
127.0.0.1 - - [14/Jul/2021 11:45:03] "POST /socket.io/?EIO=3&transport=polling&t=NgaPyf-&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
     Name  Age
0     Tom   20
1  Joseph   21
2   Krish   19
3    John   18
     Name  Age
0     Tom   20
1  Joseph   21
2   Krish   19
3    John   18
emitting event "response_output" to all [/]
2c6e85bb23aa4c83af37bc005a08837b: Sending packet MESSAGE data 2["response_output","[{\"orientation\": \"h\", \"x\": [20, 21, 19, 18], \"y\": [\"Tom\", \"Joseph\", \"Krish\", \"John\"], \"type\": \"bar\"}]"]
127.0.0.1 - - [14/Jul/2021 11:45:13] "GET /socket.io/?EIO=3&transport=polling&t=NgaPyg0&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
     Name  Age
0     Tom   20
1  Joseph   21
2   Krish   19
3    John   18
     Name  Age
0     Tom   20
1  Joseph   21
2   Krish   19
3    John   18
2c6e85bb23aa4c83af37bc005a08837b: Received packet PING data None
2c6e85bb23aa4c83af37bc005a08837b: Sending packet PONG data None
127.0.0.1 - - [14/Jul/2021 11:45:28] "GET /socket.io/?EIO=3&transport=polling&t=NgaP_9E&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
127.0.0.1 - - [14/Jul/2021 11:45:28] "POST /socket.io/?EIO=3&transport=polling&t=NgaQ2md&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
2c6e85bb23aa4c83af37bc005a08837b: Received packet PING data None
2c6e85bb23aa4c83af37bc005a08837b: Sending packet PONG data None
127.0.0.1 - - [14/Jul/2021 11:45:54] "GET /socket.io/?EIO=3&transport=polling&t=NgaQ2nA&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
127.0.0.1 - - [14/Jul/2021 11:45:54] "POST /socket.io/?EIO=3&transport=polling&t=NgaQ93n&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
2c6e85bb23aa4c83af37bc005a08837b: Received packet PING data None
2c6e85bb23aa4c83af37bc005a08837b: Sending packet PONG data None
127.0.0.1 - - [14/Jul/2021 11:46:20] "GET /socket.io/?EIO=3&transport=polling&t=NgaQ94a&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -
127.0.0.1 - - [14/Jul/2021 11:46:20] "POST /socket.io/?EIO=3&transport=polling&t=NgaQFPv&sid=2c6e85bb23aa4c83af37bc005a08837b HTTP/1.1" 200 -

First output even though dataframe is updated.

After reloading the page, the page is blank and then displays correct chart.

What I would like to achieve:

Automatically display the bar chart when dataframe is updated without reloading page.

解决方案

In function(receiving_data) found in the html page , variable plot is used instead of receiving_data. Variable plot is initialized when render_template is evoked. When data is being emitted via sockets, you do not need to pass variable plot when return render_template('views/index_img_soc.html', plot=bar) is called.

See example script below on how to emit data using sockets to the html script.

The script below will automatically update a bar chart without reloading the page.

The code using chart.js instead of plotly. There are much more examples plotting chart.js in javascript as opposed to plotly.

Updated Python script:

import random
from flask import Flask, render_template, session, request
from flask_socketio import SocketIO
from flask_executor import Executor
import pandas as pd
import json
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
thread = None



@socketio.on("response_demo")
def background_task_func():
    """Example of how to send server generated events to clients."""

    socketio.sleep(5)
    print("send")
   
    data = {'Name': ['Tom', 'Joseph', 'Krish', 'John','Shadz'], 'Age': [20, 21, 19, 18,36]} 

    data_2= pd.DataFrame(data)
    
    df_json=data_2.to_json(orient='records')
    result = {"objects": json.loads(df_json)}
    socketio.emit('my_response',result, broadcast=True)

@app.route('/')
def index():
    executor.submit(background_task_func)
    return render_template('index_2.html')



if __name__ == '__main__':
    executor = Executor(app)
    socketio.run(app)

Updated html script:

<!DOCTYPE HTML>
<html>
<head>
    <title>Flask-SocketIO Test</title>
    <script type="text/javascript" src="//code.jquery.com/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>

</head>

<canvas id="myChart" width="100" height="100"></canvas>
    <script type="text/javascript" charset="utf-8">

// Chart.js Bar Chart
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ["Red", "Blue", "Yellow", "Green", "Purple"],
        datasets: [{
            data: [2, 2,2, 2,2],
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)',
                'rgba(54, 162, 235, 0.2)',
                'rgba(255, 206, 86, 0.2)',
                'rgba(75, 192, 192, 0.2)',
                'rgba(153, 102, 255, 0.2)'
            ],
            borderColor: [
                'rgba(255,99,132,1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(75, 192, 192, 1)',
                'rgba(153, 102, 255, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
 
            yAxes: [{
                ticks: {
                    beginAtZero:true
                }
            }]
        }
    }
});


        $(document).ready(function() {

            var socket = io().connect('http://127.0.0.1:5000');
            socket.emit('response_demo')
            socket.on('my_response', function(obj) {
            for (var i=0; i < obj.objects.length; i++) {
             myChart.data.datasets[0].data[i] =obj.objects[i].Age;
             myChart.data.labels[i] =obj.objects[i].Name;
            
            }
            
                myChart.update();
           
            });

        });

</script>
</html>

742