2

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: {
        responsive: false,
        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()?

wk14
  • 197
  • 1
  • 7

1 Answers1

1

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

adelriosantiago
  • 7,762
  • 7
  • 38
  • 71
  • I actually tried putting the return statement inside `success` before, but when calling barData() in updateBarChart(), I found that the values aren't returned correctly. Only after I added the `async:false` and moved it out of `success`, then the console.log() inside `updateBarChart()` actually printed the right values, but bar chart doesn't draw/update with the values – wk14 Feb 14 '21 at 03:30
  • @wk14 Then probably the chart is not being `update`ed correctly for some reason. Could you take the CodePen I created (added to the answer above) as a starting point to add the server call? Let me know what you get. – adelriosantiago Feb 14 '21 at 06:11
  • Hey adelriosantiago, thanks again for the reply. Sorry I'm not familiar with how to use CodePen and get the server call to work without server on there..just started learning these for a month. But please see my response at the end of the post. Thanks! – wk14 Feb 15 '21 at 00:54
  • Perhaps `barChart.data.datasets.data` is incorrect. Try selecting a dataset like in the Codepen. This is, `barChart.data.datasets[0].data = ...`. Does that fix the error? – adelriosantiago Feb 15 '21 at 03:19
  • I've tried that also. That returns the error: `Uncaught TypeError: Cannot set property 'data' of undefined` for me.. :( – wk14 Feb 15 '21 at 16:58
  • 1
    You need to initialize `barchart.data` with at least one dataset. The error you are getting is the equivalent to trying to run this codepen: https://codepen.io/adelriosantiago/pen/ExNWEZJ?editors=1010. As you can see the error is the same because `myChart.data` is `[]`therefore anyting deeper that that (`myChart.data.datasets`, `myChart.data.datasets[0]` or `myChart.data.datasets[0].data`) will be undefined. – adelriosantiago Feb 15 '21 at 21:32
  • Thanks! That makes sense now. I'm now able to see the bars on canvas if I add the last block of code from my post to initialize it with default data :) I think the data={} from another post had me confused. Now if I call `updateBarChart()`, the max yaxes value does get updated, but the label isn't, and the backgroundColor only makes the bars blue for a brief second or if I hover over to the bars..otherwise in grey color. I'm wondering if this is because it falls back to the default(what I initialized) after `updateBarChart()` is called? – wk14 Feb 16 '21 at 02:06
  • Update: I found that the order of the update matters. So if I give the new value of `barChart.data.datasets[0].label` after `barChart.data.datasets[0].data`, then it actually updates the label correctly. But the bar color still only updates only if I hover on top..It's hard to find detailed documentations on .update()... – wk14 Feb 17 '21 at 21:08