Traçage de la surveillance en temps réel avec Flask et Vuejs.

 

Traçage de la surveillance en temps réel avec Flask et Vuejs.

Les données et c'est la visualisation, ce sont certaines de mes choses préférées sur lesquelles travailler. Dans ce tutoriel, je vais parler de la façon de tracer en temps réel des données du côté serveur, ces données peuvent facilement provenir de n'importe quel moteur de base de données, mais par souci de simplicité, elles proviendront d'un CSV.

J'utiliserai Flask avec Pandas côté serveur et Vuejs avec charjs côté client.

Commençons donc par générer nos données factices avec Python à l'aide de la bibliothèque Pandas :

import pandas as pd
from datetime import datetime, timedelta
import random
now = datetime.now()
configs = {
'Y1': (0, 250),
'Y2': (0, 500),
'Y3': (0, 750),
}
df_num_rows = 10000
y_vals = {i: [random.randint(*configs[i]) for j in range(df_num_rows)] for i in configs}
df = pd.DataFrame({
'X': ['{:%Y-%m-%d %H:%M:%S}'.format(now + timedelta(seconds=i)) for i in range(df_num_rows)],
**y_vals
})
df.to_csv('test_data.csv', index=False)
view rawgen_data.py hosted with ❤ by GitHub

Ce dataframe aura 10000 lignes et ressemble à ceci:

X   Y1   Y2   Y3
0     2020-12-30 11:04:54   78  309  485
1     2020-12-30 11:04:55  214  199  484
2     2020-12-30 11:04:56  101  377   18
3     2020-12-30 11:04:57  149  365  134
4     2020-12-30 11:04:58  243  177  203
5     2020-12-30 11:04:59   48  125   57
....

Implémentation côté client.

Configurons le HTML, appelons ce fichier client.html:
Remarque: je ne déclarerai pas de Doctype ou d'en-tête pour le plaisir de ce tutoriel, juste exactement ce dont nous avons besoin.

Notre récipient avec des dimensions acceptables, et en ajoutant la ligne graphique composants prêts à recevoir notre propriété dynamique ChartData :

<div id="app">
<div style="width: 600px; height: 300px;margin: 0 auto;">
<line-chart v-bind:chart-data="chartData"></line-chart>
</div>
</div>
view rawclient1.html hosted with ❤ by GitHub

Importons les bibliothèques:

<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<script src="https://unpkg.com/vue-chartjs@3.4.0/dist/vue-chartjs.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.5.1/dist/vue-resource.min.js"></script>
view rawclient2.html hosted with ❤ by GitHub

  1. Vuejs
  2. Bibliothèque Charjs
  3. Notre wrapper Chartjs pour Vuejs
  4. Ressource Vue

<script>
Vue.component('line-chart', {
extends: VueChartJs.Line,
//mixins: [VueChartJs.mixins.reactiveProp],
props: ['chartData'],
data: function() {
return {
options: {
tooltips: {
mode: 'index', // so all three tooltips appear
intersect: false, // so don't need to be precise with the cursor on the point
},
scales: {
xAxes: [{ // configs for our X axis
display: true,
scaleLabel: {
display: true,
labelString: 'Time'
}
}],
yAxes: [{ // configs for our Yaxis
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}]
},
responsive: true,
maintainAspectRatio: false,
}
}
},
watch: { // this will be our flag for update
'chartData.update_flag': function(new_val, old_val) {
this.$data._chart.update();
}
},
mounted() {
this.renderChart(this.chartData, this.options); // Initialize and render the chart
}
})
view rawclient3.html hosted with ❤ by GitHub

Il est maintenant temps pour notre instance principale de Vue:

new Vue({
el: '#app',
data() {
return {
chartData: {
'update_flag': 0, // our flag for update
'labels': [], // our labels
'datasets': [] // our datasets
},
}
},
methods: {
fillData(limit, offset) {
Vue.http.get('http://127.0.0.1:5000/?limit=' +limit+ '&offset=' +offset).then(res => {
if (offset === 0) { // if first request let's receive 20 rows of data/labels
this.chartData.labels = res.body.chart_data.labels;
this.chartData.datasets = res.body.chart_data.datasets;
} else {
this.chartData.labels.splice(0, limit); // remove the first label
this.chartData.labels.push(...res.body.chart_data.labels); // like python unpack
for (var i = 0; i < res.body.chart_data.datasets.length; i++) {
this.chartData.datasets[i].data.splice(0, limit);
this.chartData.datasets[i].data.push(...res.body.chart_data.datasets[i].data);
}
}
this.chartData.update_flag ^= 1; // toggle 0 or 1 just to trigger watch in line-chart component
}, err => {
console.log(err);
}).then(() => { // this will happen always
setTimeout(this.fillData, 1000, 1, offset+limit); // preparing next request
});
}
},
created() {
this.fillData(20, 0); // let's ask for the first 20 rows
},
})
</script>
view rawclient4.html hosted with ❤ by GitHub

PS: Cela pourrait également être fait avec des sockets Web, au lieu d'AJAX.

Pour la première demande, la réponse aura 20 valeurs pour chaque ensemble de données (données) et 20 étiquettes (axe X).

Cela ressemblera à quelque chose comme:

{
 "chart_data": {
  "datasets": [
   {
    "backgroundColor": "#483D8B",
    "borderColor": "#483D8B",
    "borderWidth": 2,
    "data": [
     32,
     112,
     102,
     ...
    ],
    "fill": false,
    "label": "name_Y1",
    "lineTension": 0,
    "pointBorderColor": "#000000",
    "pointBorderWidth": 1,
    "pointRadius": 2
   },
   {
    "backgroundColor": "#f87979",
    "borderColor": "#f87979",
    "borderWidth": 2,
    "data": [
     153,
     2,
     335,
     ...
    ],
    "fill": false,
    "label": "name_Y2",
    "lineTension": 0.23,
    "pointBorderColor": "#000000",
    "pointBorderWidth": 1,
    "pointRadius": 2
   },
   {
    "backgroundColor": "#00BFFF",
    "borderColor": "#00BFFF",
    "borderWidth": 2,
    "data": [
     267,
     262,
     232,
     ...
    ],
    "fill": false,
    "label": "name_Y3",
    "lineTension": 0.46,
    "pointBorderColor": "#000000",
    "pointBorderWidth": 1,
    "pointRadius": 2
   }
  ],
  "labels": [
   "21:33:26",
   "21:33:27",
   "21:33:28",
   ...
  ]
 }
}

{
 "chart_data": {
  "datasets": [
   {
    "backgroundColor": "#483D8B",
    "borderColor": "#483D8B",
    "borderWidth": 2,
    "data": [
     114
    ],
    "fill": false,
    "label": "name_Y1",
    "lineTension": 0,
    "pointBorderColor": "#000000",
    "pointBorderWidth": 1,
    "pointRadius": 2
   },
   {
    "backgroundColor": "#f87979",
    "borderColor": "#f87979",
    "borderWidth": 2,
    "data": [
     430
    ],
    "fill": false,
    "label": "name_Y2",
    "lineTension": 0.23,
    "pointBorderColor": "#000000",
    "pointBorderWidth": 1,
    "pointRadius": 2
   },
   {
    "backgroundColor": "#00BFFF",
    "borderColor": "#00BFFF",
    "borderWidth": 2,
    "data": [
     368
    ],
    "fill": false,
    "label": "name_Y3",
    "lineTension": 0.46,
    "pointBorderColor": "#000000",
    "pointBorderWidth": 1,
    "pointRadius": 2
   }
  ],
  "labels": [
   "21:33:46"
  ]
 }
}
Cette logique est implémentée directement dans la fonction read_csv ci-dessous, je l'expliquerai dans les commentaires.

Implémentation côté serveur.

Étant donné que la majeure partie de la logique du travail est exécutée côté client, le côté serveur sera moins déroutant et plus simple.

Appelons ce fichier server.py et commençons à importer les bibliothèques dont nous avons besoin:

from flask import Flask, request, jsonify
from flask_cors import CORS
import pandas as pd

  1. Cors, dans ce cas, parce que nos domaines sont différents, nous devons autoriser CORS
  2. Pandas

app = Flask(__name__)
CORS(app)

@app.route('/')
def hello():
offset = request.args.get('offset', default = 1, type = int) # rows to skip
limit = request.args.get('limit', default = 1, type = int) # number of rows
df = pd.read_csv('test_data.csv', # here we just import the data we need from the csv, accordding with client parameters
skiprows=range(1, offset+1), # ignore rows in the interval
nrows=limit, # limited to n rows, 1 after the first request
parse_dates=['X'])
cols = [col for col in df.columns if col.startswith('Y')]
configs = {
'Y1': {'color': '#483D8B', 'col_name': 'name_Y1'},
'Y2': {'color': '#f87979', 'col_name': 'name_Y2'},
'Y3': {'color': '#00BFFF', 'col_name': 'name_Y3'},
}
datasets = []
for k, c in enumerate(cols):
datasets.append({ # our datasets configs
'label': configs[c]['col_name'],
'borderColor': configs[c]['color'],
'backgroundColor': configs[c]['color'],
'borderWidth': 2,
'pointBorderColor': '#000000',
'lineTension': k*0.23, # line curve
'pointRadius': 2,
'pointBorderWidth': 1,
'fill': False,
'data': df[c].tolist()
})
chart = {
'labels': df['X'].dt.strftime('%H:%M:%S').tolist(),
'datasets': datasets
}
return jsonify({'chart_data': chart})
app.run()
view rawserver1.py hosted with ❤ by GitHub

Et nous sommes tous prêts:

Résultat final

Exécutez simplement:

$ python3 server.py

Ensuite, double-cliquez sur le fichier client.html, il devrait ouvrir un navigateur Web.

Quoi qu'il en soit, je sais que le code ici peut être un peu déroutant, alors voici le projet Github .


Commentaires

Posts les plus consultés de ce blog

Comment fonctionne l'optimise d'Adam

Comment utiliser les diagrammes PlantUML dans Visual Studio Code pour Windows 10

VIDEO SURVEILLANCE AVEC RASPBERRY PI ET FLASK