I am trying reproduce this project that consists of an app with a back-end component in Flask and a front-end that uses vue-js. The back-end is the following:
from flask import Flask, request, jsonify
from flask_cors import CORS
import pandas as pd
app = Flask(__name__)
CORS(app)
@app.route('/')
def hello():
offset = request.args.get('offset', default = 1, type = int)
limit = request.args.get('limit', default = 1, type = int)
df = pd.read_csv('test_data.csv', skiprows=range(1, offset 1), nrows=limit, 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({
'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()
where the csv file is generated with the following script:
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 # ex: {**{'a': [1, 2, 3], 'b': [4, 5, 6]}}
})
df.to_csv('test_data.csv', index=False)y_vals = {i: [random.randint(*configs[i]) for j in range(df_num_rows)] for i in configs}
The html file is:
<div id="app">
<div style="width: 600px; height: 300px;margin: 0 auto;">
<line-chart v-bind:chart-data="chartData"></line-chart>
</div>
</div>
<script src="https://unpkg.com/vue"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-chartjs.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-resource.min.js"></script>
<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
}
})
var vm = 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;
}, 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>
When I launch the app and perform a GET request through the web-browser (end point: localhost:5000?limit=100&offset=100
for example), the web-page shows a string corresponding to the json, i.e., it is not rendering the html page, because, I think, it does not know it has to render the file index.html
. Nevertheless, the front-end is revceiving some data.
If I change the code to return flask.render_template('client.html', chart_data=jsonify({'chart_data': chart}
, in the front-end I've noticed that this.chartData.labels
has 0 length, as if the front-end is not receiving the data correctly, the same for this.chartData.datasets
.
How to properly render the html file in such an application?
CodePudding user response:
You should have two functions in flask - with two different URLs.
First function with url /
should send only template - and this URL without limit=100&offset=100
you should load in browser.
Second function with url ie. /get_data
should send JSON data - and Vue.http.get()
should use it with limit=100&offset=100
like /get_data?limit=100&offset=100
If you want to do it with single url then you should use some if/else
to detect if page is loaded by browser and send HTML
- or by Vue
and send JSON
. Maybe you could use some HTTP header with informat that you expecte response with JSON
. Accept: application/json
Full working code with two functions
from flask import Flask, request, jsonify, render_template_string
from flask_cors import CORS
import pandas as pd
app = Flask(__name__)
CORS(app)
def generate_data():
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 # ex: {**{'a': [1, 2, 3], 'b': [4, 5, 6]}}
})
df.to_csv('test_data.csv', index=False)
#y_vals = {i: [random.randint(*configs[i]) for j in range(df_num_rows)] for i in configs}
# ------
@app.route('/')
def index():
return render_template_string('''
<div id="app">
<div style="width: 600px; height: 300px;margin: 0 auto;">
<line-chart v-bind:chart-data="chartData"></line-chart>
</div>
</div>
<script src="https://unpkg.com/vue"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-chartjs.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-resource.min.js"></script>
<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
}
})
var vm = 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://localhost:5000/get_data?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;
}, 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>
''')
@app.route('/get_data')
def get_data():
offset = request.args.get('offset', default = 1, type = int)
limit = request.args.get('limit', default = 1, type = int)
df = pd.read_csv('test_data.csv', skiprows=range(1, offset 1), nrows=limit, 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({
'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})
if __name__ == '__main__':
generate_data()
app.run()