1

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?

1
  • I don't see index.html in flask code - so it doesn't render it because you didn't use template for this. You should have two functions in flask - one should use return render_template('index.html') and second should send JSON data. And Vue.http.get() should use URL for the second function. Commented Oct 27, 2021 at 11:29

1 Answer 1

1

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()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.