Home > Software design >  Vue.js not rendering html file with data returned by a Flask back-end
Vue.js not rendering html file with data returned by a Flask back-end

Time:10-28

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()
  • Related