2
votes

I'm building my app with Flask and MongoDB.

Right now I'm having some hard time trying to figure out how to allow my bar chart to change dynamically according to my different ajax requests based on a drop down menu..

Before figuring out how to make the graph responsive to my selection in drop down menu, I decided to work on making the graph to be able to react to different ajax data and update accordingly.

First, I learned how to create a basic bar chart with below:

var getValues = $.get('/data/locations-vs-counts/');
getValues.done(function(bar_data){
    counts = bar_data.counts;
    label = bar_data.locations;
    maxValue = Math.max.apply(Math, counts);
    var barchart = new Chart(ctx, {
        type'bar',
        data: {
            labels: label,
            datasets: [{
            label'# of counts',
            backgroundColor'rgb(17, 146, 232)',
            borderColor'rgb(17, 146, 232)',
            data: counts
        }]
    },
    options: {
        responsivefalse,
        scales: {
            yAxes: [{
         id'y-axis-1'type'linear'position'left'ticks:{beginAtZero:true,max:(maxValue+1)}
        }]
    }
    }
});
});

But in order to achieve what I want, I think I need to create a function for the chart to be able to take some inputs (URL, chart_title, label, etc..). I decided to try with only the URL input for my first attempt.
I've read the following posts for my reference:
Refresh the Bar Char in ChartJS (v2.6.0) Using Ajax Correct Way
How to return multiple arrays from a function in Javascript?
https://github.com/chartjs/Chart.js/issues/6176
Updating chartJS with dynamic data
How to GET data in Flask from AJAX post (not used yet but I think will be useful when need to get chartjs to update with dropdown)

I ended up with the following codes so far:

// Bar chart (# of Counts vs Locations)
var ctx = document.getElementById('barChart').getContext('2d');
var barChart = new Chart(ctx,{
    type: 'bar',
    data:[],
    option:{responsive: false}
});

function barData(url){
    let data_labels = [];
    let data_values = [];
    $.ajax({
        url: url,
        type: 'GET',
        async: false,
        success: function(bar_data){
            data_labels= bar_data.labels;
            data_values= bar_data.values;
        }
    })
    return {data_labels,data_values};
}

function updateBarChart(url_link){
    let results = barData(url_link);
    counts = results.data_values;
    xlabels = results.data_labels;
    maxValue = Math.max.apply(Math, counts);
    console.log("counts:",counts);
    console.log("xlabels:", xlabels);
    barChart.data.labels = xlabels;
    barChart.data.datasets.data = counts;
    barChart.data.datasets.label = "# of counts";
    barChart.data.datasets.backgroundColor = 'rgb(17, 146, 232)';
    barChart.options.scales.yAxes[0].ticks.beginAtZero= true;
    barChart.options.scales.yAxes[0].ticks.max = (maxValue+1);
    barChart.update();
}
updateBarChart('/data/locations-vs-counts/')

However..the above did not work as expected.
The canvas shows up with the correct xlabels and y scales, but the bars were not drawn. But I can see the counts variable has the array with correct values in console.
I also tried barChart.data.datasets[0].data = counts; like some post suggested, but error was shown:

Uncaught TypeError: Cannot set property 'data' of undefined at updateBarChart

For reference, the data I get from the URL are two lists that I generated from pandas dataframe like below: data_parse.py:

@data_parser.route('/locations-vs-counts/', methods=['GET'])
def locations_vs_counts():
    locations = location_list()   #from another function
    df = all_counts()
    df = df['location'].value_counts()
    for location in locations:
        if(location not in df.index):
            df.loc[location] = 0
    labels = df.index.tolist()
    counts = df.tolist()
    return jsonify({'labels':labels, 'values': counts})

Could someone please tell me what I did wrong with the updateBarChart() function?
And if this approach is doable with I want to achieve eventually: BarChart with all the locations vs counts as the default chart, then user can select from dropdown of locations to look at details of that location(2 new arrays coming from another route)
Any help is appreciated!! Thank you in advance!

Response to adelriosantiago's suggestion:
I'm able to see my bar chart drawn correctly with the following

var results = barData('/data/locations-vs-counts/');  //same barData() function from above
var myValues = results.data_values;
var myLabels = results.data_labels;

var myChart = new Chart(ctx, {
        type: 'bar',
        data: {
            labels: myLabels,
            datasets: [{
                label: '# of counts',
                data: myValues
            }]
        },
        options:{
            responsive: false
        }
});

So I think this proves that the barData() is getting the values as expected and stored in myLabels and myValues correctly, though the async:false doesn't seem ideal. I'm not guessing that it has something to do with how I update the graph data inside updateBarChart()?

1

1 Answers

1
votes

This is an asynchronous vs synchorous error. This tutorial may help a bit.

Take a look at this part of the code:

function barData(url){
    let data_labels = [];
    let data_values = [];
    $.ajax({
        url: url,
        type: 'GET',
        async: false,
        success: function(bar_data){
            data_labels= bar_data.labels;
            data_values= bar_data.values;
        }
    })
    return {data_labels,data_values};
}

The return {data_labels,data_values}; is actually happening before the $.ajax call. If you want to return the results from the server this line should be inside the success function of the ajax call. Take a look at this question where the OP has the same issue.

About updating a Chart, here is a demo with a mocked server response that updates its values each second: https://codepen.io/adelriosantiago/pen/YzpNLbd